Skip to content

Commit b7f6d93

Browse files
committed
fix: add missing secrets module to repository
1 parent c31d80d commit b7f6d93

4 files changed

Lines changed: 323 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ Thumbs.db
2727
# Secrets
2828
*.pem
2929
*.key
30-
secrets/
30+
/secrets/
31+
!terraform/modules/secrets/
3132
.env
3233
.env.*
3334

terraform/modules/secrets/main.tf

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright (c) 2024 Bima Kharisma Wicaksana
3+
* GitHub: https://github.com/bimakw
4+
*
5+
* Secret Manager Module
6+
* Creates and manages secrets in Google Secret Manager
7+
*/
8+
9+
# Create secrets from map
10+
resource "google_secret_manager_secret" "secrets" {
11+
for_each = var.secrets
12+
13+
project = var.project_id
14+
secret_id = "${var.project_name}-${each.key}"
15+
16+
replication {
17+
auto {}
18+
}
19+
20+
labels = merge(
21+
{
22+
environment = var.environment
23+
project = var.project_name
24+
managed_by = "terraform"
25+
},
26+
each.value.labels
27+
)
28+
29+
# Rotation configuration (if specified)
30+
dynamic "rotation" {
31+
for_each = each.value.rotation_period != "" ? [1] : []
32+
content {
33+
rotation_period = each.value.rotation_period
34+
next_rotation_time = each.value.next_rotation_time
35+
}
36+
}
37+
38+
# Expiration (if specified)
39+
dynamic "topics" {
40+
for_each = each.value.pubsub_topic != "" ? [1] : []
41+
content {
42+
name = each.value.pubsub_topic
43+
}
44+
}
45+
}
46+
47+
# Create secret versions
48+
resource "google_secret_manager_secret_version" "versions" {
49+
for_each = { for k, v in var.secrets : k => v if v.secret_data != "" }
50+
51+
secret = google_secret_manager_secret.secrets[each.key].id
52+
secret_data = each.value.secret_data
53+
54+
depends_on = [google_secret_manager_secret.secrets]
55+
}
56+
57+
# IAM bindings for GKE workload identity
58+
resource "google_secret_manager_secret_iam_member" "workload_identity" {
59+
for_each = { for item in flatten([
60+
for secret_key, secret_value in var.secrets : [
61+
for sa in secret_value.accessor_service_accounts : {
62+
secret_key = secret_key
63+
service_account = sa
64+
}
65+
]
66+
]) : "${item.secret_key}-${item.service_account}" => item }
67+
68+
project = var.project_id
69+
secret_id = google_secret_manager_secret.secrets[each.value.secret_key].secret_id
70+
role = "roles/secretmanager.secretAccessor"
71+
member = "serviceAccount:${each.value.service_account}"
72+
}
73+
74+
# Default application secrets
75+
resource "google_secret_manager_secret" "app_secrets" {
76+
for_each = var.create_default_secrets ? toset(["jwt-secret", "api-key", "encryption-key"]) : toset([])
77+
78+
project = var.project_id
79+
secret_id = "${var.project_name}-${each.value}"
80+
81+
replication {
82+
auto {}
83+
}
84+
85+
labels = {
86+
environment = var.environment
87+
project = var.project_name
88+
type = "application"
89+
managed_by = "terraform"
90+
}
91+
}
92+
93+
# Generate random values for default secrets
94+
resource "random_password" "jwt_secret" {
95+
count = var.create_default_secrets ? 1 : 0
96+
97+
length = 64
98+
special = true
99+
override_special = "!@#$%^&*()_+-=[]{}|;:,.<>?"
100+
}
101+
102+
resource "random_password" "api_key" {
103+
count = var.create_default_secrets ? 1 : 0
104+
105+
length = 32
106+
special = false
107+
}
108+
109+
resource "random_password" "encryption_key" {
110+
count = var.create_default_secrets ? 1 : 0
111+
112+
length = 32
113+
special = false
114+
}
115+
116+
# Secret versions for default secrets
117+
resource "google_secret_manager_secret_version" "jwt_secret" {
118+
count = var.create_default_secrets ? 1 : 0
119+
120+
secret = google_secret_manager_secret.app_secrets["jwt-secret"].id
121+
secret_data = random_password.jwt_secret[0].result
122+
}
123+
124+
resource "google_secret_manager_secret_version" "api_key" {
125+
count = var.create_default_secrets ? 1 : 0
126+
127+
secret = google_secret_manager_secret.app_secrets["api-key"].id
128+
secret_data = random_password.api_key[0].result
129+
}
130+
131+
resource "google_secret_manager_secret_version" "encryption_key" {
132+
count = var.create_default_secrets ? 1 : 0
133+
134+
secret = google_secret_manager_secret.app_secrets["encryption-key"].id
135+
secret_data = random_password.encryption_key[0].result
136+
}
137+
138+
# Grant access to default secrets for specified service accounts
139+
resource "google_secret_manager_secret_iam_member" "default_secrets_access" {
140+
for_each = { for item in flatten([
141+
for secret in(var.create_default_secrets ? ["jwt-secret", "api-key", "encryption-key"] : []) : [
142+
for sa in var.default_secret_accessors : {
143+
secret = secret
144+
service_account = sa
145+
}
146+
]
147+
]) : "${item.secret}-${item.service_account}" => item }
148+
149+
project = var.project_id
150+
secret_id = google_secret_manager_secret.app_secrets[each.value.secret].secret_id
151+
role = "roles/secretmanager.secretAccessor"
152+
member = "serviceAccount:${each.value.service_account}"
153+
}
154+
155+
# Service Account for accessing secrets from GKE
156+
resource "google_service_account" "secret_accessor" {
157+
count = var.create_secret_accessor_sa ? 1 : 0
158+
159+
account_id = "${var.project_name}-secret-accessor"
160+
project = var.project_id
161+
display_name = "Secret Accessor Service Account"
162+
description = "Service account for accessing secrets from GKE workloads"
163+
}
164+
165+
# Workload Identity binding
166+
resource "google_service_account_iam_member" "workload_identity_binding" {
167+
count = var.create_secret_accessor_sa && var.gke_namespace != "" ? 1 : 0
168+
169+
service_account_id = google_service_account.secret_accessor[0].name
170+
role = "roles/iam.workloadIdentityUser"
171+
member = "serviceAccount:${var.project_id}.svc.id.goog[${var.gke_namespace}/${var.gke_service_account_name}]"
172+
}
173+
174+
# Grant secret accessor role to the SA
175+
resource "google_project_iam_member" "secret_accessor_role" {
176+
count = var.create_secret_accessor_sa ? 1 : 0
177+
178+
project = var.project_id
179+
role = "roles/secretmanager.secretAccessor"
180+
member = "serviceAccount:${google_service_account.secret_accessor[0].email}"
181+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2024 Bima Kharisma Wicaksana
3+
* Secret Manager Module - Outputs
4+
*/
5+
6+
output "secret_ids" {
7+
description = "Map of secret IDs"
8+
value = { for k, v in google_secret_manager_secret.secrets : k => v.secret_id }
9+
}
10+
11+
output "secret_names" {
12+
description = "Map of secret names (full resource names)"
13+
value = { for k, v in google_secret_manager_secret.secrets : k => v.name }
14+
}
15+
16+
output "secret_version_ids" {
17+
description = "Map of secret version IDs"
18+
value = { for k, v in google_secret_manager_secret_version.versions : k => v.id }
19+
}
20+
21+
# Default secrets outputs
22+
output "jwt_secret_id" {
23+
description = "JWT Secret ID"
24+
value = var.create_default_secrets ? google_secret_manager_secret.app_secrets["jwt-secret"].secret_id : null
25+
}
26+
27+
output "api_key_secret_id" {
28+
description = "API Key Secret ID"
29+
value = var.create_default_secrets ? google_secret_manager_secret.app_secrets["api-key"].secret_id : null
30+
}
31+
32+
output "encryption_key_secret_id" {
33+
description = "Encryption Key Secret ID"
34+
value = var.create_default_secrets ? google_secret_manager_secret.app_secrets["encryption-key"].secret_id : null
35+
}
36+
37+
# Service Account outputs
38+
output "secret_accessor_sa_email" {
39+
description = "Secret Accessor Service Account Email"
40+
value = var.create_secret_accessor_sa ? google_service_account.secret_accessor[0].email : null
41+
}
42+
43+
output "secret_accessor_sa_name" {
44+
description = "Secret Accessor Service Account Name"
45+
value = var.create_secret_accessor_sa ? google_service_account.secret_accessor[0].name : null
46+
}
47+
48+
# Helper output for mounting secrets in GKE
49+
output "gke_secret_store_config" {
50+
description = "Configuration for GKE Secret Store CSI Driver"
51+
value = var.create_default_secrets ? {
52+
provider = "gcp"
53+
secrets = {
54+
jwt_secret = {
55+
resourceName = "projects/${var.project_id}/secrets/${var.project_name}-jwt-secret/versions/latest"
56+
key = "jwt-secret"
57+
}
58+
api_key = {
59+
resourceName = "projects/${var.project_id}/secrets/${var.project_name}-api-key/versions/latest"
60+
key = "api-key"
61+
}
62+
encryption_key = {
63+
resourceName = "projects/${var.project_id}/secrets/${var.project_name}-encryption-key/versions/latest"
64+
key = "encryption-key"
65+
}
66+
}
67+
} : null
68+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2024 Bima Kharisma Wicaksana
3+
* Secret Manager Module - Variables
4+
*/
5+
6+
# Project Configuration
7+
variable "project_id" {
8+
description = "GCP Project ID"
9+
type = string
10+
}
11+
12+
variable "project_name" {
13+
description = "Project name for resource naming"
14+
type = string
15+
}
16+
17+
variable "region" {
18+
description = "GCP Region"
19+
type = string
20+
default = "asia-southeast1"
21+
}
22+
23+
variable "environment" {
24+
description = "Environment (dev, staging, prod)"
25+
type = string
26+
}
27+
28+
# Secrets Configuration
29+
variable "secrets" {
30+
description = "Map of secrets to create"
31+
type = map(object({
32+
secret_data = string
33+
labels = map(string)
34+
rotation_period = string
35+
next_rotation_time = string
36+
pubsub_topic = string
37+
accessor_service_accounts = list(string)
38+
}))
39+
default = {}
40+
}
41+
42+
# Default Application Secrets
43+
variable "create_default_secrets" {
44+
description = "Create default application secrets (jwt-secret, api-key, encryption-key)"
45+
type = bool
46+
default = true
47+
}
48+
49+
variable "default_secret_accessors" {
50+
description = "Service accounts that can access default secrets"
51+
type = list(string)
52+
default = []
53+
}
54+
55+
# Secret Accessor Service Account
56+
variable "create_secret_accessor_sa" {
57+
description = "Create a dedicated service account for secret access"
58+
type = bool
59+
default = true
60+
}
61+
62+
variable "gke_namespace" {
63+
description = "GKE namespace for workload identity binding"
64+
type = string
65+
default = "default"
66+
}
67+
68+
variable "gke_service_account_name" {
69+
description = "Kubernetes service account name for workload identity"
70+
type = string
71+
default = "default"
72+
}

0 commit comments

Comments
 (0)