Skip to content

Commit

Permalink
Add support for GCF Gen2 CMEK (#8518) (#6004)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <magic-modules@google.com>
Co-authored-by: Cameron Thornton <camthornton@google.com>
  • Loading branch information
modular-magician and c2thorn authored Aug 2, 2023
1 parent 3ac889d commit bd5aa3e
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/8518.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
cloudfunctionsv2: added support for GCF Gen2 CMEK
```
147 changes: 147 additions & 0 deletions google-beta/resource_cloudfunctions2_function_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,153 @@ resource "google_cloudfunctions2_function" "function" {
`, context)
}

func TestAccCloudfunctions2function_cloudfunctions2CmekExample(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"project": envvar.GetTestProjectFromEnv(),
"kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name,
"zip_path": "./test-fixtures/cloudfunctions2/function-source.zip",
"location": "us-central1",
"random_suffix": acctest.RandString(t, 10),
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t),
CheckDestroy: testAccCheckCloudfunctions2functionDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccCloudfunctions2function_cloudfunctions2CmekExample(context),
},
{
ResourceName: "google_cloudfunctions2_function.function",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"location", "build_config.0.source.0.storage_source.0.object", "build_config.0.source.0.storage_source.0.bucket"},
},
},
})
}

func testAccCloudfunctions2function_cloudfunctions2CmekExample(context map[string]interface{}) string {
return acctest.Nprintf(`
locals {
project = "%{project}" # Google Cloud Platform Project ID
}
data "google_project" "project" {
provider = google-beta
}
resource "google_storage_bucket" "bucket" {
provider = google-beta
name = "${local.project}-tf-test-gcf-source%{random_suffix}" # Every bucket name must be globally unique
location = "US"
uniform_bucket_level_access = true
}
resource "google_storage_bucket_object" "object" {
provider = google-beta
name = "function-source.zip"
bucket = google_storage_bucket.bucket.name
source = "%{zip_path}" # Add path to the zipped function source code
}
resource "google_project_service_identity" "ea_sa" {
provider = google-beta
project = data.google_project.project.project_id
service = "eventarc.googleapis.com"
}
resource "google_artifact_registry_repository" "unencoded-ar-repo" {
provider = google-beta
repository_id = "tf-test-ar-repo%{random_suffix}"
location = "us-central1"
format = "DOCKER"
}
resource "google_artifact_registry_repository_iam_binding" "binding" {
provider = google-beta
location = google_artifact_registry_repository.encoded-ar-repo.location
repository = google_artifact_registry_repository.encoded-ar-repo.name
role = "roles/artifactregistry.admin"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
]
}
resource "google_kms_crypto_key_iam_binding" "gcf_cmek_keyuser" {
provider = google-beta
crypto_key_id = "%{kms_key_name}"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gs-project-accounts.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@serverless-robot-prod.iam.gserviceaccount.com",
"serviceAccount:${google_project_service_identity.ea_sa.email}",
]
depends_on = [
google_project_service_identity.ea_sa
]
}
resource "google_artifact_registry_repository" "encoded-ar-repo" {
provider = google-beta
location = "us-central1"
repository_id = "tf-test-cmek-repo%{random_suffix}"
format = "DOCKER"
kms_key_name = "%{kms_key_name}"
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}
resource "google_cloudfunctions2_function" "function" {
provider = google-beta
name = "tf-test-function-cmek%{random_suffix}"
location = "us-central1"
description = "CMEK function"
kms_key_name = "%{kms_key_name}"
build_config {
runtime = "nodejs16"
entry_point = "helloHttp" # Set the entry point
docker_repository = google_artifact_registry_repository.encoded-ar-repo.id
source {
storage_source {
bucket = google_storage_bucket.bucket.name
object = google_storage_bucket_object.object.name
}
}
}
service_config {
max_instance_count = 1
available_memory = "256M"
timeout_seconds = 60
}
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}
`, context)
}

func testAccCheckCloudfunctions2functionDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ region. If not provided, defaults to the same region as the function.`,
},
},
},
"kms_key_name": {
Type: schema.TypeString,
Optional: true,
Description: `Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources.
It must match the pattern projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}.`,
},
"labels": {
Type: schema.TypeMap,
Optional: true,
Expand Down Expand Up @@ -548,6 +554,12 @@ func resourceCloudfunctions2functionCreate(d *schema.ResourceData, meta interfac
} else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
obj["labels"] = labelsProp
}
kmsKeyNameProp, err := expandCloudfunctions2functionKmsKeyName(d.Get("kms_key_name"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("kms_key_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(kmsKeyNameProp)) && (ok || !reflect.DeepEqual(v, kmsKeyNameProp)) {
obj["kmsKeyName"] = kmsKeyNameProp
}

url, err := tpgresource.ReplaceVars(d, config, "{{Cloudfunctions2BasePath}}projects/{{project}}/locations/{{location}}/functions?functionId={{name}}")
if err != nil {
Expand Down Expand Up @@ -687,6 +699,9 @@ func resourceCloudfunctions2functionRead(d *schema.ResourceData, meta interface{
if err := d.Set("labels", flattenCloudfunctions2functionLabels(res["labels"], d, config)); err != nil {
return fmt.Errorf("Error reading function: %s", err)
}
if err := d.Set("kms_key_name", flattenCloudfunctions2functionKmsKeyName(res["kmsKeyName"], d, config)); err != nil {
return fmt.Errorf("Error reading function: %s", err)
}

return nil
}
Expand Down Expand Up @@ -737,6 +752,12 @@ func resourceCloudfunctions2functionUpdate(d *schema.ResourceData, meta interfac
} else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
obj["labels"] = labelsProp
}
kmsKeyNameProp, err := expandCloudfunctions2functionKmsKeyName(d.Get("kms_key_name"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("kms_key_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, kmsKeyNameProp)) {
obj["kmsKeyName"] = kmsKeyNameProp
}

url, err := tpgresource.ReplaceVars(d, config, "{{Cloudfunctions2BasePath}}projects/{{project}}/locations/{{location}}/functions/{{name}}")
if err != nil {
Expand Down Expand Up @@ -765,6 +786,10 @@ func resourceCloudfunctions2functionUpdate(d *schema.ResourceData, meta interfac
if d.HasChange("labels") {
updateMask = append(updateMask, "labels")
}

if d.HasChange("kms_key_name") {
updateMask = append(updateMask, "kmsKeyName")
}
// updateMask is a URL parameter but not present in the schema, so ReplaceVars
// won't set it
url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
Expand Down Expand Up @@ -1429,6 +1454,10 @@ func flattenCloudfunctions2functionLabels(v interface{}, d *schema.ResourceData,
return v
}

func flattenCloudfunctions2functionKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func expandCloudfunctions2functionName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/functions/{{name}}")
}
Expand Down Expand Up @@ -2182,3 +2211,7 @@ func expandCloudfunctions2functionLabels(v interface{}, d tpgresource.TerraformR
}
return m, nil
}

func expandCloudfunctions2functionKmsKeyName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}
123 changes: 123 additions & 0 deletions website/docs/r/cloudfunctions2_function.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,124 @@ resource "google_cloudfunctions2_function" "function" {
}
}
```
## Example Usage - Cloudfunctions2 Cmek


```hcl
locals {
project = "my-project-name" # Google Cloud Platform Project ID
}
data "google_project" "project" {
provider = google-beta
}
resource "google_storage_bucket" "bucket" {
provider = google-beta
name = "${local.project}-gcf-source" # Every bucket name must be globally unique
location = "US"
uniform_bucket_level_access = true
}
resource "google_storage_bucket_object" "object" {
provider = google-beta
name = "function-source.zip"
bucket = google_storage_bucket.bucket.name
source = "function-source.zip" # Add path to the zipped function source code
}
resource "google_project_service_identity" "ea_sa" {
provider = google-beta
project = data.google_project.project.project_id
service = "eventarc.googleapis.com"
}
resource "google_artifact_registry_repository" "unencoded-ar-repo" {
provider = google-beta
repository_id = "ar-repo"
location = "us-central1"
format = "DOCKER"
}
resource "google_artifact_registry_repository_iam_binding" "binding" {
provider = google-beta
location = google_artifact_registry_repository.encoded-ar-repo.location
repository = google_artifact_registry_repository.encoded-ar-repo.name
role = "roles/artifactregistry.admin"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
]
}
resource "google_kms_crypto_key_iam_binding" "gcf_cmek_keyuser" {
provider = google-beta
crypto_key_id = "cmek-key"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gs-project-accounts.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@serverless-robot-prod.iam.gserviceaccount.com",
"serviceAccount:${google_project_service_identity.ea_sa.email}",
]
depends_on = [
google_project_service_identity.ea_sa
]
}
resource "google_artifact_registry_repository" "encoded-ar-repo" {
provider = google-beta
location = "us-central1"
repository_id = "cmek-repo"
format = "DOCKER"
kms_key_name = "cmek-key"
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}
resource "google_cloudfunctions2_function" "function" {
provider = google-beta
name = "function-cmek"
location = "us-central1"
description = "CMEK function"
kms_key_name = "cmek-key"
build_config {
runtime = "nodejs16"
entry_point = "helloHttp" # Set the entry point
docker_repository = google_artifact_registry_repository.encoded-ar-repo.id
source {
storage_source {
bucket = google_storage_bucket.bucket.name
object = google_storage_bucket_object.object.name
}
}
}
service_config {
max_instance_count = 1
available_memory = "256M"
timeout_seconds = 60
}
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}
```

## Argument Reference

Expand Down Expand Up @@ -680,6 +798,11 @@ The following arguments are supported:
(Optional)
A set of key/value label pairs associated with this Cloud Function.

* `kms_key_name` -
(Optional)
Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources.
It must match the pattern projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}.

* `location` -
(Optional)
The location of this cloud function.
Expand Down

0 comments on commit bd5aa3e

Please sign in to comment.