Skip to content

Commit 78dc870

Browse files
Konstantin Apanchyonokbharathkkb
andauthored
feat: Add support for using secret manager environment variables in Cloud Function (#88)
* Add support for using secret manager environment variables * Add example of usage secret_environment_variables * keep sa * workaround for permadiff Co-authored-by: bharathkkb <[email protected]>
1 parent a9fba93 commit 78dc870

File tree

12 files changed

+103
-16
lines changed

12 files changed

+103
-16
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ module "localhost_function" {
7373
| project\_id | The ID of the project to which resources will be applied. | `string` | n/a | yes |
7474
| region | The region in which resources will be applied. | `string` | n/a | yes |
7575
| runtime | The runtime in which the function will be executed. | `string` | n/a | yes |
76+
| secret\_environment\_variables | A list of maps which contains key, project\_id, secret\_name (not the full secret id) and version to assign to the function as a set of secret environment variables. | `list(map(string))` | `[]` | no |
7677
| service\_account\_email | The service account to run the function as. | `string` | `""` | no |
7778
| source\_dependent\_files | A list of any Terraform created `local_file`s that the module will wait for before creating the archive. | <pre>list(object({<br> filename = string<br> id = string<br> }))</pre> | `[]` | no |
7879
| source\_directory | The pathname of the directory which contains the function source code. | `string` | n/a | yes |
@@ -111,6 +112,7 @@ the following IAM roles:
111112

112113
- Cloud Functions Developer: `roles/cloudfunctions.developer`
113114
- Storage Admin: `roles/storage.admin`
115+
- Secret Manager Accessor: `roles/secretmanager.secretAccessor`
114116

115117
### APIs
116118

@@ -119,6 +121,7 @@ following APIs enabled:
119121

120122
- Cloud Functions API: `cloudfunctions.googleapis.com`
121123
- Cloud Storage API: `storage-component.googleapis.com`
124+
- Secret Manager API: `secretmanager.googleapis.com`
122125

123126
The [Project Factory module][project-factory-module-site] can be used to
124127
provision projects with specific APIs activated.

examples/dynamic-files/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
This example demonstrates how to use the
44
[root module][root-module] that will contain source
5-
code files generated from Terraform itself.
5+
code files generated from Terraform itself and
6+
environment variable stored in the Secrets Manager.
67

78
## Usage
89

@@ -29,6 +30,7 @@ this directory:
2930
| function\_name | The name of the function created |
3031
| project\_id | The project in which resources are applied. |
3132
| random\_file\_string | The content of the terraform created file in the source directory. |
33+
| random\_secret\_string | The value of the secret variable. |
3234
| region | The region in which resources are applied. |
3335

3436
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
@@ -46,7 +48,7 @@ must also be met.
4648
The following software dependencies must be installed on the system
4749
from which this module will be invoked:
4850

49-
- [Terraform][terraform-site] v0.12
51+
- [Terraform][terraform-site] v0.13
5052

5153
### IAM Roles
5254

@@ -56,6 +58,7 @@ the following IAM roles:
5658
- Logs Configuration Writer: `roles/logging.configWriter`
5759
- Pub/Sub Admin: `roles/pubsub.admin`
5860
- Service Account User: `roles/iam.serviceAccountUser`
61+
- Secret Manager Admin: `roles/secretmanager.admin`
5962

6063
- Default AppSpot user: `roles/owner`
6164
- Your user: `roles/resourcemanager.projectCreator`
@@ -67,6 +70,7 @@ following APIs enabled:
6770

6871
- Cloud Pub/Sub API: `pubsub.googleapis.com`
6972
- Stackdriver Logging API: `logging.googleapis.com`
73+
- Secret Manager API: `secretmanager.googleapis.com`
7074

7175
[event-folder-log-entry-submodule-requirements]: ../../modules/event-folder-log-entry/README.md#requirements
7276
[event-folder-log-entry-submodule]: ../../modules/event-folder-log-entry

examples/dynamic-files/function_source/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ const filePath = path.join(__dirname, "terraform_created_file.txt");
2727
exports.fileContent = (data, context, callback) => {
2828
console.log("Received event");
2929

30+
console.log("Value from KEY env variable: " + process.env.KEY);
3031
fs.readFile(filePath, { encoding: "utf-8" }, function(err, data) {
3132
if (!err) {
32-
callback(null, data);
33+
callback(null, data + "\n" + process.env.KEY);
3334
} else {
3435
callback(err);
3536
}

examples/dynamic-files/main.tf

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17+
locals {
18+
secret_key_name = "random_secret_key"
19+
}
20+
1721
resource "random_pet" "main" {
1822
length = 2
1923
separator = "-"
@@ -28,12 +32,43 @@ resource "google_storage_bucket" "trigger_bucket" {
2832
}
2933

3034
resource "random_string" "random" {
35+
count = 2
3136
length = 16
3237
special = false
3338
}
3439

40+
resource "google_secret_manager_secret" "secret_key" {
41+
project = var.project_id
42+
secret_id = local.secret_key_name
43+
44+
replication {
45+
user_managed {
46+
replicas {
47+
location = var.region
48+
}
49+
}
50+
}
51+
}
52+
53+
resource "google_secret_manager_secret_version" "secret_key_version" {
54+
secret = google_secret_manager_secret.secret_key.id
55+
secret_data = random_string.random[0].result
56+
depends_on = [
57+
google_secret_manager_secret.secret_key
58+
]
59+
}
60+
61+
resource "google_secret_manager_secret_iam_member" "iam_access_policy" {
62+
secret_id = google_secret_manager_secret.secret_key.id
63+
role = "roles/secretmanager.secretAccessor"
64+
member = "serviceAccount:${var.project_id}@appspot.gserviceaccount.com"
65+
depends_on = [
66+
google_secret_manager_secret.secret_key
67+
]
68+
}
69+
3570
resource "local_file" "file" {
36-
content = random_string.random.result
71+
content = random_string.random[1].result
3772
filename = "${path.module}/function_source/terraform_created_file.txt"
3873
}
3974

@@ -48,13 +83,23 @@ module "localhost_function" {
4883
resource = google_storage_bucket.trigger_bucket.name
4984
}
5085

86+
secret_environment_variables = [
87+
{
88+
key = "KEY"
89+
project_id = var.project_id
90+
secret_name = local.secret_key_name
91+
version = "1"
92+
}
93+
]
94+
5195
name = random_pet.main.id
5296
project_id = var.project_id
5397
region = var.region
5498
source_directory = "${path.module}/function_source"
5599
runtime = "nodejs10"
56100

57101
source_dependent_files = [local_file.file]
102+
depends_on = [google_secret_manager_secret_version.secret_key_version]
58103
}
59104

60105
resource "null_resource" "wait_for_function" {

examples/dynamic-files/outputs.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ output "function_name" {
2929
description = "The name of the function created"
3030
}
3131

32+
output "random_secret_string" {
33+
value = random_string.random[0].result
34+
description = "The value of the secret variable."
35+
}
36+
3237
output "random_file_string" {
33-
value = random_string.random.result
38+
value = random_string.random[1].result
3439
description = "The content of the terraform created file in the source directory."
3540
}

examples/dynamic-files/versions.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ terraform {
1818
required_version = ">= 0.13"
1919
required_providers {
2020
google = {
21-
source = "hashicorp/google"
21+
source = "hashicorp/google"
22+
version = ">= 4.11"
2223
}
2324
local = {
2425
source = "hashicorp/local"

main.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ data "archive_file" "main" {
5858
excludes = var.files_to_exclude_in_source_dir
5959
}
6060

61+
6162
resource "google_storage_bucket" "main" {
6263
count = var.create_bucket ? 1 : 0
6364
name = coalesce(var.bucket_name, var.name)
@@ -87,6 +88,12 @@ resource "google_storage_bucket_object" "main" {
8788
content_type = "application/zip"
8889
}
8990

91+
// todo(bharathkkb): remove workaround after https://github.com/hashicorp/terraform-provider-google/issues/11383
92+
data "google_project" "nums" {
93+
for_each = toset(concat(compact([for item in var.secret_environment_variables : lookup(item, "project_id", "")]), [var.project_id]))
94+
project_id = each.value
95+
}
96+
9097
resource "google_cloudfunctions_function" "main" {
9198
name = var.name
9299
description = var.description
@@ -112,6 +119,17 @@ resource "google_cloudfunctions_function" "main" {
112119
}
113120
}
114121

122+
dynamic "secret_environment_variables" {
123+
for_each = { for item in var.secret_environment_variables : item.key => item }
124+
125+
content {
126+
key = secret_environment_variables.value["key"]
127+
project_id = data.google_project.nums[lookup(secret_environment_variables.value, "project_id", var.project_id)].number
128+
secret = secret_environment_variables.value["secret_name"]
129+
version = lookup(secret_environment_variables.value, "version", "latest")
130+
}
131+
}
132+
115133
labels = var.labels
116134
runtime = var.runtime
117135
environment_variables = var.environment_variables

test/integration/dynamic-files/dynamic_files_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ func TestDynamicFiles(t *testing.T) {
3636
region := bpt.GetStringOutput("region")
3737
functionName := bpt.GetStringOutput("function_name")
3838
randomFileString := bpt.GetStringOutput("random_file_string")
39+
randomSecretString := bpt.GetStringOutput("random_secret_string")
3940

4041
// call the function directly
4142
op := gcloud.Run(t,
4243
fmt.Sprintf("functions call %s", functionName),
4344
gcloud.WithCommonArgs([]string{"--data", "{}", "--format", "json", "--project", project, "--region", region}),
4445
)
45-
// assert random string is contained in function response
46-
assert.Contains(op.Get("result").String(), randomFileString, "contains random string")
46+
// assert file random string and secret random string is contained in function response
47+
assert.Contains(op.Get("result").String(), randomFileString, "contains file random string")
48+
assert.Contains(op.Get("result").String(), randomSecretString, "contains secret random string")
4749
})
4850

4951
bpt.Test()

test/setup/iam.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ locals {
2323
"roles/logging.configWriter",
2424
"roles/source.admin",
2525
"roles/iam.serviceAccountUser",
26+
"roles/secretmanager.admin",
2627
]
2728

2829
int_required_folder_roles = [

test/setup/main.tf

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ module "project" {
3232
source = "terraform-google-modules/project-factory/google"
3333
version = "~> 10.0"
3434

35-
name = local.project_name
36-
random_project_id = true
37-
org_id = var.org_id
38-
folder_id = var.folder_id
39-
billing_account = var.billing_account
35+
name = local.project_name
36+
random_project_id = true
37+
org_id = var.org_id
38+
folder_id = var.folder_id
39+
billing_account = var.billing_account
40+
default_service_account = "keep"
4041

4142
activate_apis = [
4243
"cloudresourcemanager.googleapis.com",
@@ -46,7 +47,8 @@ module "project" {
4647
"cloudfunctions.googleapis.com",
4748
"storage-component.googleapis.com",
4849
"sourcerepo.googleapis.com",
49-
"compute.googleapis.com"
50+
"compute.googleapis.com",
51+
"secretmanager.googleapis.com",
5052
]
5153
}
5254

0 commit comments

Comments
 (0)