Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unblock EXTERNAL/EXTERNAL_VPC Cloud KMS key creation. #9931

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions mmv1/products/kms/CryptoKey.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,10 @@ properties:
description: |
Whether this key may contain imported versions only.
default_from_api: true
- !ruby/object:Api::Type::String
name: 'cryptoKeyBackend'
immutable: true
description: |
The resource name of the backend environment associated with all CryptoKeyVersions within this CryptoKey.
The resource name is in the format "projects/*/locations/*/ekmConnections/*" and only applies to "EXTERNAL_VPC" keys.
default_from_api: true
17 changes: 17 additions & 0 deletions mmv1/products/kms/CryptoKeyVersion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ examples:
custom_code: !ruby/object:Provider::Terraform::CustomCode
custom_delete: templates/terraform/custom_delete/kms_crypto_key_version.erb
custom_import: templates/terraform/custom_import/kms_crypto_key_version.go.erb
pre_update: templates/terraform/pre_update/kms_crypto_key_version.go.erb
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
parameters:
- !ruby/object:Api::Type::String
name: 'cryptoKey'
Expand Down Expand Up @@ -123,6 +124,9 @@ properties:
name: 'externalProtectionLevelOptions'
description: |
ExternalProtectionLevelOptions stores a group of additional fields for configuring a CryptoKeyVersion that are specific to the EXTERNAL protection level and EXTERNAL_VPC protection levels.
deprecation_message: >-
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
`externalProtectionLevelOptions` is being un-nested from the `attestation` field.
Please use the top level `externalProtectionLevelOptions` field instead.
properties:
- !ruby/object:Api::Type::String
name: 'externalKeyUri'
Expand All @@ -132,3 +136,16 @@ properties:
name: 'ekmConnectionKeyPath'
description: |
The path to the external key material on the EKM when using EkmConnection e.g., "v0/my/key". Set this field instead of externalKeyUri when using an EkmConnection.
- !ruby/object:Api::Type::NestedObject
name: 'externalProtectionLevelOptions'
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
description: |
ExternalProtectionLevelOptions stores a group of additional fields for configuring a CryptoKeyVersion that are specific to the EXTERNAL protection level and EXTERNAL_VPC protection levels.
properties:
- !ruby/object:Api::Type::String
name: 'externalKeyUri'
description: |
The URI for an external resource that this CryptoKeyVersion represents.
- !ruby/object:Api::Type::String
name: 'ekmConnectionKeyPath'
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
description: |
The path to the external key material on the EKM when using EkmConnection e.g., "v0/my/key". Set this field instead of externalKeyUri when using an EkmConnection.
20 changes: 20 additions & 0 deletions mmv1/templates/terraform/pre_update/kms_crypto_key_version.go.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// The generated code does not support conditional update masks.
newUpdateMask := []string{}
if d.HasChange("state") {
newUpdateMask = append(newUpdateMask, "state")
}

// Validate updated fields based on protection level (EXTERNAL vs EXTERNAL_VPC)
if d.HasChange("external_protection_level_options") {
if d.Get("protection_level") == "EXTERNAL" {
newUpdateMask = append(newUpdateMask, "externalProtectionLevelOptions.externalKeyUri")
} else if d.Get("protection_level") == "EXTERNAL_VPC" {
newUpdateMask = append(newUpdateMask, "externalProtectionLevelOptions.ekmConnectionKeyPath")
}
}
// 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(newUpdateMask, ",")})
if err != nil {
return err
}
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,85 @@ func TestAccKmsCryptoKeyVersion_patch(t *testing.T) {
})
}

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

projectId := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
projectOrg := envvar.GetTestOrgFromEnv(t)
projectBillingAccount := envvar.GetTestBillingAccountFromEnv(t)
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
keyUri := "data.google_secret_manager_secret_version.key_uri.secret_data"
updatedKeyUri := "data.google_secret_manager_secret_version.key_uri_updated.secret_data"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptions(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, keyUri),
},
{
ResourceName: "google_kms_crypto_key_version.crypto_key_version",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
},
{
Config: testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptions(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, updatedKeyUri),
},
{
ResourceName: "google_kms_crypto_key_version.crypto_key_version",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
},
},
})
}

func TestAccKmsCryptoKeyVersion_externalProtectionLevelOptionsVpc(t *testing.T) {
// This test relies on manual steps to set up the EkmConnection used for the
// CryptoKeyVersion creation, which means we can't spin up a temporary project.
// We also can't use bootstrapped keys because that would defeat the purpose of
// this key creation test, so we skip this test for VCR to avoid KMS resource
// accumulation in the TF test project (since KMS resources can't be deleted).
acctest.SkipIfVcr(t)
t.Parallel()

projectId := envvar.GetTestProjectFromEnv()
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
ekmConnectionName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
keyPath := "data.google_secret_manager_secret_version.key_path.secret_data"
updatedKeyPath := "data.google_secret_manager_secret_version.key_path_updated.secret_data"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptionsVpc(projectId, keyRingName, cryptoKeyName, ekmConnectionName, keyPath),
},
{
ResourceName: "google_kms_crypto_key_version.crypto_key_version",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
},
{
Config: testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptionsVpc(projectId, keyRingName, cryptoKeyName, ekmConnectionName, updatedKeyPath),
},
{
ResourceName: "google_kms_crypto_key_version.crypto_key_version",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
},
},
})
}

// This test runs in its own project, otherwise the test project would start to get filled
// with undeletable resources
func testGoogleKmsCryptoKey_basic(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName string) string {
Expand Down Expand Up @@ -953,3 +1032,147 @@ resource "google_kms_crypto_key_version" "crypto_key_version" {
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, preventDestroy, state)
}

func testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptions(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, keyUri string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
name = "%s"
project_id = "%s"
org_id = "%s"
billing_account = "%s"
}

resource "google_project_service" "acceptance" {
c2thorn marked this conversation as resolved.
Show resolved Hide resolved
project = google_project.acceptance.project_id
service = "cloudkms.googleapis.com"
}

resource "google_kms_key_ring" "key_ring" {
project = google_project_service.acceptance.project
name = "%s"
location = "us-central1"
}

resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = google_kms_key_ring.key_ring.id

version_template {
algorithm = "EXTERNAL_SYMMETRIC_ENCRYPTION"
protection_level = "EXTERNAL"
}

labels = {
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
key = "value"
}
skip_initial_version_creation = true
}

data "google_secret_manager_secret_version" "key_uri" {
secret = "external-full-key-uri"
project = "315636579862"
}
data "google_secret_manager_secret_version" "key_uri_updated" {
secret = "external-full-key-uri-update-test"
project = "315636579862"
}

resource "google_kms_crypto_key_version" "crypto_key_version" {
crypto_key = google_kms_crypto_key.crypto_key.id
external_protection_level_options {
external_key_uri = %s
}
}
`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, keyUri)
}

// EkmConnection setup and creation is based off of resource_kms_ekm_connection_test.go
func testGoogleKmsCryptoKeyVersion_externalProtectionLevelOptionsVpc(projectId, keyRingName, cryptoKeyName, ekmConnectionName, keyPath string) string {
return fmt.Sprintf(`
data "google_project" "vpc-project" {
project_id = "cloud-ekm-refekm-playground"
}
data "google_project" "project" {
project_id = "%s"
}

data "google_secret_manager_secret_version" "raw_der" {
secret = "playground-cert"
project = "315636579862"
}
data "google_secret_manager_secret_version" "hostname" {
secret = "external-uri"
project = "315636579862"
}
data "google_secret_manager_secret_version" "servicedirectoryservice" {
secret = "external-servicedirectoryservice"
project = "315636579862"
}

resource "google_project_iam_member" "add_sdviewer" {
tdbhacks marked this conversation as resolved.
Show resolved Hide resolved
project = data.google_project.vpc-project.number
role = "roles/servicedirectory.viewer"
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-ekms.iam.gserviceaccount.com"
}
resource "google_project_iam_member" "add_pscAuthorizedService" {
project = data.google_project.vpc-project.number
role = "roles/servicedirectory.pscAuthorizedService"
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-ekms.iam.gserviceaccount.com"
}

resource "google_kms_ekm_connection" "example-ekmconnection" {
name = "%s"
location = "us-central1"
key_management_mode = "MANUAL"
service_resolvers {
service_directory_service = data.google_secret_manager_secret_version.servicedirectoryservice.secret_data
hostname = data.google_secret_manager_secret_version.hostname.secret_data
server_certificates {
raw_der = data.google_secret_manager_secret_version.raw_der.secret_data
}
}
depends_on = [
google_project_iam_member.add_pscAuthorizedService,
google_project_iam_member.add_sdviewer
]
}

resource "google_kms_key_ring" "key_ring" {
project = data.google_project.project.project_id
name = "%s"
location = "us-central1"
}

resource "google_kms_crypto_key" "crypto_key" {
name = "%s"
key_ring = google_kms_key_ring.key_ring.id

version_template {
algorithm = "EXTERNAL_SYMMETRIC_ENCRYPTION"
protection_level = "EXTERNAL_VPC"
}

labels = {
key = "value"
}
crypto_key_backend = google_kms_ekm_connection.example-ekmconnection.id
skip_initial_version_creation = true
}

data "google_secret_manager_secret_version" "key_path" {
secret = "external-keypath"
project = "315636579862"
}
data "google_secret_manager_secret_version" "key_path_updated" {
secret = "external-keypath-update-test"
project = "315636579862"
}

resource "google_kms_crypto_key_version" "crypto_key_version" {
crypto_key = google_kms_crypto_key.crypto_key.id
external_protection_level_options {
ekm_connection_key_path = %s
}
}
`, projectId, ekmConnectionName, keyRingName, cryptoKeyName, keyPath)
}