Skip to content

Commit

Permalink
feat: Add support for using secret manager environment variables in C…
Browse files Browse the repository at this point in the history
…loud 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]>
  • Loading branch information
AronllStone and bharathkkb authored Apr 14, 2022
1 parent a9fba93 commit 78dc870
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 16 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ module "localhost_function" {
| project\_id | The ID of the project to which resources will be applied. | `string` | n/a | yes |
| region | The region in which resources will be applied. | `string` | n/a | yes |
| runtime | The runtime in which the function will be executed. | `string` | n/a | yes |
| 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 |
| service\_account\_email | The service account to run the function as. | `string` | `""` | no |
| 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 |
| source\_directory | The pathname of the directory which contains the function source code. | `string` | n/a | yes |
Expand Down Expand Up @@ -111,6 +112,7 @@ the following IAM roles:

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

### APIs

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

- Cloud Functions API: `cloudfunctions.googleapis.com`
- Cloud Storage API: `storage-component.googleapis.com`
- Secret Manager API: `secretmanager.googleapis.com`

The [Project Factory module][project-factory-module-site] can be used to
provision projects with specific APIs activated.
Expand Down
8 changes: 6 additions & 2 deletions examples/dynamic-files/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

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

## Usage

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

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

- [Terraform][terraform-site] v0.12
- [Terraform][terraform-site] v0.13

### IAM Roles

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

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

- Cloud Pub/Sub API: `pubsub.googleapis.com`
- Stackdriver Logging API: `logging.googleapis.com`
- Secret Manager API: `secretmanager.googleapis.com`

[event-folder-log-entry-submodule-requirements]: ../../modules/event-folder-log-entry/README.md#requirements
[event-folder-log-entry-submodule]: ../../modules/event-folder-log-entry
Expand Down
3 changes: 2 additions & 1 deletion examples/dynamic-files/function_source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ const filePath = path.join(__dirname, "terraform_created_file.txt");
exports.fileContent = (data, context, callback) => {
console.log("Received event");

console.log("Value from KEY env variable: " + process.env.KEY);
fs.readFile(filePath, { encoding: "utf-8" }, function(err, data) {
if (!err) {
callback(null, data);
callback(null, data + "\n" + process.env.KEY);
} else {
callback(err);
}
Expand Down
47 changes: 46 additions & 1 deletion examples/dynamic-files/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
* limitations under the License.
*/

locals {
secret_key_name = "random_secret_key"
}

resource "random_pet" "main" {
length = 2
separator = "-"
Expand All @@ -28,12 +32,43 @@ resource "google_storage_bucket" "trigger_bucket" {
}

resource "random_string" "random" {
count = 2
length = 16
special = false
}

resource "google_secret_manager_secret" "secret_key" {
project = var.project_id
secret_id = local.secret_key_name

replication {
user_managed {
replicas {
location = var.region
}
}
}
}

resource "google_secret_manager_secret_version" "secret_key_version" {
secret = google_secret_manager_secret.secret_key.id
secret_data = random_string.random[0].result
depends_on = [
google_secret_manager_secret.secret_key
]
}

resource "google_secret_manager_secret_iam_member" "iam_access_policy" {
secret_id = google_secret_manager_secret.secret_key.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${var.project_id}@appspot.gserviceaccount.com"
depends_on = [
google_secret_manager_secret.secret_key
]
}

resource "local_file" "file" {
content = random_string.random.result
content = random_string.random[1].result
filename = "${path.module}/function_source/terraform_created_file.txt"
}

Expand All @@ -48,13 +83,23 @@ module "localhost_function" {
resource = google_storage_bucket.trigger_bucket.name
}

secret_environment_variables = [
{
key = "KEY"
project_id = var.project_id
secret_name = local.secret_key_name
version = "1"
}
]

name = random_pet.main.id
project_id = var.project_id
region = var.region
source_directory = "${path.module}/function_source"
runtime = "nodejs10"

source_dependent_files = [local_file.file]
depends_on = [google_secret_manager_secret_version.secret_key_version]
}

resource "null_resource" "wait_for_function" {
Expand Down
7 changes: 6 additions & 1 deletion examples/dynamic-files/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ output "function_name" {
description = "The name of the function created"
}

output "random_secret_string" {
value = random_string.random[0].result
description = "The value of the secret variable."
}

output "random_file_string" {
value = random_string.random.result
value = random_string.random[1].result
description = "The content of the terraform created file in the source directory."
}
3 changes: 2 additions & 1 deletion examples/dynamic-files/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ terraform {
required_version = ">= 0.13"
required_providers {
google = {
source = "hashicorp/google"
source = "hashicorp/google"
version = ">= 4.11"
}
local = {
source = "hashicorp/local"
Expand Down
18 changes: 18 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ data "archive_file" "main" {
excludes = var.files_to_exclude_in_source_dir
}


resource "google_storage_bucket" "main" {
count = var.create_bucket ? 1 : 0
name = coalesce(var.bucket_name, var.name)
Expand Down Expand Up @@ -87,6 +88,12 @@ resource "google_storage_bucket_object" "main" {
content_type = "application/zip"
}

// todo(bharathkkb): remove workaround after https://github.com/hashicorp/terraform-provider-google/issues/11383
data "google_project" "nums" {
for_each = toset(concat(compact([for item in var.secret_environment_variables : lookup(item, "project_id", "")]), [var.project_id]))
project_id = each.value
}

resource "google_cloudfunctions_function" "main" {
name = var.name
description = var.description
Expand All @@ -112,6 +119,17 @@ resource "google_cloudfunctions_function" "main" {
}
}

dynamic "secret_environment_variables" {
for_each = { for item in var.secret_environment_variables : item.key => item }

content {
key = secret_environment_variables.value["key"]
project_id = data.google_project.nums[lookup(secret_environment_variables.value, "project_id", var.project_id)].number
secret = secret_environment_variables.value["secret_name"]
version = lookup(secret_environment_variables.value, "version", "latest")
}
}

labels = var.labels
runtime = var.runtime
environment_variables = var.environment_variables
Expand Down
6 changes: 4 additions & 2 deletions test/integration/dynamic-files/dynamic_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ func TestDynamicFiles(t *testing.T) {
region := bpt.GetStringOutput("region")
functionName := bpt.GetStringOutput("function_name")
randomFileString := bpt.GetStringOutput("random_file_string")
randomSecretString := bpt.GetStringOutput("random_secret_string")

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

bpt.Test()
Expand Down
1 change: 1 addition & 0 deletions test/setup/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ locals {
"roles/logging.configWriter",
"roles/source.admin",
"roles/iam.serviceAccountUser",
"roles/secretmanager.admin",
]

int_required_folder_roles = [
Expand Down
14 changes: 8 additions & 6 deletions test/setup/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ module "project" {
source = "terraform-google-modules/project-factory/google"
version = "~> 10.0"

name = local.project_name
random_project_id = true
org_id = var.org_id
folder_id = var.folder_id
billing_account = var.billing_account
name = local.project_name
random_project_id = true
org_id = var.org_id
folder_id = var.folder_id
billing_account = var.billing_account
default_service_account = "keep"

activate_apis = [
"cloudresourcemanager.googleapis.com",
Expand All @@ -46,7 +47,8 @@ module "project" {
"cloudfunctions.googleapis.com",
"storage-component.googleapis.com",
"sourcerepo.googleapis.com",
"compute.googleapis.com"
"compute.googleapis.com",
"secretmanager.googleapis.com",
]
}

Expand Down
7 changes: 6 additions & 1 deletion variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ variable "runtime" {
description = "The runtime in which the function will be executed."
}

variable "secret_environment_variables" {
type = list(map(string))
default = []
description = "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."
}

variable "source_directory" {
type = string
description = "The pathname of the directory which contains the function source code."
Expand Down Expand Up @@ -172,4 +178,3 @@ variable "log_object_prefix" {
default = null
description = "Log object prefix"
}

2 changes: 1 addition & 1 deletion versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ terraform {

google = {
source = "hashicorp/google"
version = ">= 3.53, < 5.0"
version = ">= 4.11, < 5.0"
}
}

Expand Down

0 comments on commit 78dc870

Please sign in to comment.