diff --git a/.changelog/10094.txt b/.changelog/10094.txt new file mode 100644 index 0000000000..282f6569ff --- /dev/null +++ b/.changelog/10094.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_kms_ekm_connection` +``` \ No newline at end of file diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go index f7510c23dc..603f1c3874 100644 --- a/google-beta/provider/provider_mmv1_resources.go +++ b/google-beta/provider/provider_mmv1_resources.go @@ -433,9 +433,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 445 +// Generated resources: 446 // Generated IAM resources: 264 -// Total generated resources: 709 +// Total generated resources: 710 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -935,6 +935,7 @@ var generatedResources = map[string]*schema.Resource{ "google_integration_connectors_endpoint_attachment": integrationconnectors.ResourceIntegrationConnectorsEndpointAttachment(), "google_kms_crypto_key": kms.ResourceKMSCryptoKey(), "google_kms_crypto_key_version": kms.ResourceKMSCryptoKeyVersion(), + "google_kms_ekm_connection": kms.ResourceKMSEkmConnection(), "google_kms_key_ring": kms.ResourceKMSKeyRing(), "google_kms_key_ring_import_job": kms.ResourceKMSKeyRingImportJob(), "google_kms_secret_ciphertext": kms.ResourceKMSSecretCiphertext(), diff --git a/google-beta/services/kms/resource_kms_ekm_connection.go b/google-beta/services/kms/resource_kms_ekm_connection.go new file mode 100644 index 0000000000..443f861a85 --- /dev/null +++ b/google-beta/services/kms/resource_kms_ekm_connection.go @@ -0,0 +1,766 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package kms + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/verify" +) + +func ResourceKMSEkmConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceKMSEkmConnectionCreate, + Read: resourceKMSEkmConnectionRead, + Update: resourceKMSEkmConnectionUpdate, + Delete: resourceKMSEkmConnectionDelete, + + Importer: &schema.ResourceImporter{ + State: resourceKMSEkmConnectionImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + CustomizeDiff: customdiff.All( + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The location for the EkmConnection. +A full list of valid locations can be found by running 'gcloud kms locations list'.`, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareResourceNames, + Description: `The resource name for the EkmConnection.`, + }, + "service_resolvers": { + Type: schema.TypeList, + Required: true, + Description: `A list of ServiceResolvers where the EKM can be reached. There should be one ServiceResolver per EKM replica. Currently, only a single ServiceResolver is supported`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hostname": { + Type: schema.TypeString, + Required: true, + Description: `Required. The hostname of the EKM replica used at TLS and HTTP layers.`, + }, + "server_certificates": { + Type: schema.TypeList, + Required: true, + Description: `Required. A list of leaf server certificates used to authenticate HTTPS connections to the EKM replica. Currently, a maximum of 10 Certificate is supported.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "raw_der": { + Type: schema.TypeString, + Required: true, + Description: `Required. The raw certificate bytes in DER format. A base64-encoded string.`, + }, + "issuer": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The issuer distinguished name in RFC 2253 format. Only present if parsed is true.`, + }, + "not_after_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The certificate is not valid after this time. Only present if parsed is true. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, + }, + "not_before_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The certificate is not valid before this time. Only present if parsed is true. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, + }, + "parsed": { + Type: schema.TypeBool, + Computed: true, + Description: `Output only. True if the certificate was parsed successfully.`, + }, + "serial_number": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The certificate serial number as a hex string. Only present if parsed is true.`, + }, + "sha256_fingerprint": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The SHA-256 certificate fingerprint as a hex string. Only present if parsed is true.`, + }, + "subject": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The subject distinguished name in RFC 2253 format. Only present if parsed is true.`, + }, + "subject_alternative_dns_names": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Description: `Output only. The subject Alternative DNS names. Only present if parsed is true.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "service_directory_service": { + Type: schema.TypeString, + Required: true, + Description: `Required. The resource name of the Service Directory service pointing to an EKM replica, in the format projects/*/locations/*/namespaces/*/services/*`, + }, + "endpoint_filter": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `Optional. The filter applied to the endpoints of the resolved service. If no filter is specified, all endpoints will be considered. An endpoint will be chosen arbitrarily from the filtered list for each request. For endpoint filter syntax and examples, see https://cloud.google.com/service-directory/docs/reference/rpc/google.cloud.servicedirectory.v1#resolveservicerequest.`, + }, + }, + }, + }, + "crypto_space_path": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `Optional. Identifies the EKM Crypto Space that this EkmConnection maps to. Note: This field is required if KeyManagementMode is CLOUD_KMS.`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `Optional. Etag of the currently stored EkmConnection.`, + }, + "key_management_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidateEnum([]string{"MANUAL", "CLOUD_KMS", ""}), + Description: `Optional. Describes who can perform control plane operations on the EKM. If unset, this defaults to MANUAL Default value: "MANUAL" Possible values: ["MANUAL", "CLOUD_KMS"]`, + Default: "MANUAL", + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The time at which the EkmConnection was created. +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceKMSEkmConnectionCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + nameProp, err := expandKMSEkmConnectionName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + serviceResolversProp, err := expandKMSEkmConnectionServiceResolvers(d.Get("service_resolvers"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("service_resolvers"); !tpgresource.IsEmptyValue(reflect.ValueOf(serviceResolversProp)) && (ok || !reflect.DeepEqual(v, serviceResolversProp)) { + obj["serviceResolvers"] = serviceResolversProp + } + keyManagementModeProp, err := expandKMSEkmConnectionKeyManagementMode(d.Get("key_management_mode"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("key_management_mode"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyManagementModeProp)) && (ok || !reflect.DeepEqual(v, keyManagementModeProp)) { + obj["keyManagementMode"] = keyManagementModeProp + } + etagProp, err := expandKMSEkmConnectionEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(etagProp)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + cryptoSpacePathProp, err := expandKMSEkmConnectionCryptoSpacePath(d.Get("crypto_space_path"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("crypto_space_path"); !tpgresource.IsEmptyValue(reflect.ValueOf(cryptoSpacePathProp)) && (ok || !reflect.DeepEqual(v, cryptoSpacePathProp)) { + obj["cryptoSpacePath"] = cryptoSpacePathProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/ekmConnections?ekmConnectionId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new EkmConnection: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for EkmConnection: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return fmt.Errorf("Error creating EkmConnection: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating EkmConnection %q: %#v", d.Id(), res) + + return resourceKMSEkmConnectionRead(d, meta) +} + +func resourceKMSEkmConnectionRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for EkmConnection: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("KMSEkmConnection %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + + if err := d.Set("name", flattenKMSEkmConnectionName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + if err := d.Set("service_resolvers", flattenKMSEkmConnectionServiceResolvers(res["serviceResolvers"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + if err := d.Set("key_management_mode", flattenKMSEkmConnectionKeyManagementMode(res["keyManagementMode"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + if err := d.Set("etag", flattenKMSEkmConnectionEtag(res["etag"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + if err := d.Set("crypto_space_path", flattenKMSEkmConnectionCryptoSpacePath(res["cryptoSpacePath"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + if err := d.Set("create_time", flattenKMSEkmConnectionCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading EkmConnection: %s", err) + } + + return nil +} + +func resourceKMSEkmConnectionUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for EkmConnection: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + serviceResolversProp, err := expandKMSEkmConnectionServiceResolvers(d.Get("service_resolvers"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("service_resolvers"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, serviceResolversProp)) { + obj["serviceResolvers"] = serviceResolversProp + } + keyManagementModeProp, err := expandKMSEkmConnectionKeyManagementMode(d.Get("key_management_mode"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("key_management_mode"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, keyManagementModeProp)) { + obj["keyManagementMode"] = keyManagementModeProp + } + etagProp, err := expandKMSEkmConnectionEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + cryptoSpacePathProp, err := expandKMSEkmConnectionCryptoSpacePath(d.Get("crypto_space_path"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("crypto_space_path"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, cryptoSpacePathProp)) { + obj["cryptoSpacePath"] = cryptoSpacePathProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating EkmConnection %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("service_resolvers") { + updateMask = append(updateMask, "serviceResolvers") + } + + if d.HasChange("key_management_mode") { + updateMask = append(updateMask, "keyManagementMode") + } + + if d.HasChange("etag") { + updateMask = append(updateMask, "etag") + } + + if d.HasChange("crypto_space_path") { + updateMask = append(updateMask, "cryptoSpacePath") + } + // 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, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + // if updateMask is empty we are not updating anything so skip the post + if len(updateMask) > 0 { + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + + if err != nil { + return fmt.Errorf("Error updating EkmConnection %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating EkmConnection %q: %#v", d.Id(), res) + } + + } + + return resourceKMSEkmConnectionRead(d, meta) +} + +func resourceKMSEkmConnectionDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[WARNING] KMS EkmConnection resources"+ + " cannot be deleted from Google Cloud. The resource %s will be removed from Terraform"+ + " state, but will still be present on Google Cloud.", d.Id()) + d.SetId("") + + return nil +} + +func resourceKMSEkmConnectionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^projects/(?P[^/]+)/locations/(?P[^/]+)/ekmConnections/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)$", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenKMSEkmConnectionName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.NameFromSelfLinkStateFunc(v) +} + +func flattenKMSEkmConnectionServiceResolvers(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "service_directory_service": flattenKMSEkmConnectionServiceResolversServiceDirectoryService(original["serviceDirectoryService"], d, config), + "hostname": flattenKMSEkmConnectionServiceResolversHostname(original["hostname"], d, config), + "server_certificates": flattenKMSEkmConnectionServiceResolversServerCertificates(original["serverCertificates"], d, config), + "endpoint_filter": flattenKMSEkmConnectionServiceResolversEndpointFilter(original["endpointFilter"], d, config), + }) + } + return transformed +} +func flattenKMSEkmConnectionServiceResolversServiceDirectoryService(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversHostname(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificates(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "raw_der": flattenKMSEkmConnectionServiceResolversServerCertificatesRawDer(original["rawDer"], d, config), + "parsed": flattenKMSEkmConnectionServiceResolversServerCertificatesParsed(original["parsed"], d, config), + "issuer": flattenKMSEkmConnectionServiceResolversServerCertificatesIssuer(original["issuer"], d, config), + "subject": flattenKMSEkmConnectionServiceResolversServerCertificatesSubject(original["subject"], d, config), + "not_before_time": flattenKMSEkmConnectionServiceResolversServerCertificatesNotBeforeTime(original["notBeforeTime"], d, config), + "not_after_time": flattenKMSEkmConnectionServiceResolversServerCertificatesNotAfterTime(original["notAfterTime"], d, config), + "sha256_fingerprint": flattenKMSEkmConnectionServiceResolversServerCertificatesSha256Fingerprint(original["sha256Fingerprint"], d, config), + "serial_number": flattenKMSEkmConnectionServiceResolversServerCertificatesSerialNumber(original["serialNumber"], d, config), + "subject_alternative_dns_names": flattenKMSEkmConnectionServiceResolversServerCertificatesSubjectAlternativeDnsNames(original["subjectAlternativeDnsNames"], d, config), + }) + } + return transformed +} +func flattenKMSEkmConnectionServiceResolversServerCertificatesRawDer(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesParsed(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesIssuer(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesSubject(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesNotBeforeTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesNotAfterTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesSha256Fingerprint(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesSerialNumber(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversServerCertificatesSubjectAlternativeDnsNames(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionServiceResolversEndpointFilter(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionKeyManagementMode(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionEtag(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionCryptoSpacePath(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenKMSEkmConnectionCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandKMSEkmConnectionName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolvers(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedServiceDirectoryService, err := expandKMSEkmConnectionServiceResolversServiceDirectoryService(original["service_directory_service"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceDirectoryService); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["serviceDirectoryService"] = transformedServiceDirectoryService + } + + transformedHostname, err := expandKMSEkmConnectionServiceResolversHostname(original["hostname"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHostname); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["hostname"] = transformedHostname + } + + transformedServerCertificates, err := expandKMSEkmConnectionServiceResolversServerCertificates(original["server_certificates"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServerCertificates); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["serverCertificates"] = transformedServerCertificates + } + + transformedEndpointFilter, err := expandKMSEkmConnectionServiceResolversEndpointFilter(original["endpoint_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEndpointFilter); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["endpointFilter"] = transformedEndpointFilter + } + + req = append(req, transformed) + } + return req, nil +} + +func expandKMSEkmConnectionServiceResolversServiceDirectoryService(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversHostname(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificates(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedRawDer, err := expandKMSEkmConnectionServiceResolversServerCertificatesRawDer(original["raw_der"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRawDer); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["rawDer"] = transformedRawDer + } + + transformedParsed, err := expandKMSEkmConnectionServiceResolversServerCertificatesParsed(original["parsed"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedParsed); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["parsed"] = transformedParsed + } + + transformedIssuer, err := expandKMSEkmConnectionServiceResolversServerCertificatesIssuer(original["issuer"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIssuer); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["issuer"] = transformedIssuer + } + + transformedSubject, err := expandKMSEkmConnectionServiceResolversServerCertificatesSubject(original["subject"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSubject); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["subject"] = transformedSubject + } + + transformedNotBeforeTime, err := expandKMSEkmConnectionServiceResolversServerCertificatesNotBeforeTime(original["not_before_time"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNotBeforeTime); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["notBeforeTime"] = transformedNotBeforeTime + } + + transformedNotAfterTime, err := expandKMSEkmConnectionServiceResolversServerCertificatesNotAfterTime(original["not_after_time"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNotAfterTime); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["notAfterTime"] = transformedNotAfterTime + } + + transformedSha256Fingerprint, err := expandKMSEkmConnectionServiceResolversServerCertificatesSha256Fingerprint(original["sha256_fingerprint"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSha256Fingerprint); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["sha256Fingerprint"] = transformedSha256Fingerprint + } + + transformedSerialNumber, err := expandKMSEkmConnectionServiceResolversServerCertificatesSerialNumber(original["serial_number"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSerialNumber); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["serialNumber"] = transformedSerialNumber + } + + transformedSubjectAlternativeDnsNames, err := expandKMSEkmConnectionServiceResolversServerCertificatesSubjectAlternativeDnsNames(original["subject_alternative_dns_names"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSubjectAlternativeDnsNames); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["subjectAlternativeDnsNames"] = transformedSubjectAlternativeDnsNames + } + + req = append(req, transformed) + } + return req, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesRawDer(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesParsed(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesIssuer(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesSubject(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesNotBeforeTime(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesNotAfterTime(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesSha256Fingerprint(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesSerialNumber(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversServerCertificatesSubjectAlternativeDnsNames(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionServiceResolversEndpointFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionKeyManagementMode(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionEtag(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandKMSEkmConnectionCryptoSpacePath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/services/kms/resource_kms_ekm_connection_test.go b/google-beta/services/kms/resource_kms_ekm_connection_test.go new file mode 100644 index 0000000000..e950cdc855 --- /dev/null +++ b/google-beta/services/kms/resource_kms_ekm_connection_test.go @@ -0,0 +1,141 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package kms_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" +) + +func TestAccKMSEkmConnection_kmsEkmConnectionBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccKMSEkmConnection_kmsEkmConnectionBasicExample_full(context), + }, + { + ResourceName: "google_kms_ekm_connection.example-ekmconnection", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name"}, + }, + { + Config: testAccKMSEkmConnection_kmsEkmConnectionBasicExample_update(context), + }, + { + ResourceName: "google_kms_ekm_connection.example-ekmconnection", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "name"}, + }, + }, + }) +} + +func testAccKMSEkmConnection_kmsEkmConnectionBasicExample_full(context map[string]interface{}) string { + return acctest.Nprintf(` +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" +} +data "google_project" "vpc-project" { + project_id = "cloud-ekm-refekm-playground" +} +data "google_project" "project" { +} +resource "google_project_iam_member" "add_sdviewer" { + 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 = "tf_test_ekmconnection_example%{random_suffix}" + 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 + ] +} +`, context) +} + +func testAccKMSEkmConnection_kmsEkmConnectionBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +data "google_project" "vpc-project" { + project_id = "cloud-ekm-refekm-playground" +} +data "google_project" "project" { +} +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_updateekmconnection" { + 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_updateekmconnection" { + 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 = "tf_test_ekmconnection_example%{random_suffix}" + location = "us-central1" + key_management_mode = "CLOUD_KMS" + crypto_space_path = "v0/longlived/crypto-space-placeholder" + 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_updateekmconnection, + google_project_iam_member.add_sdviewer_updateekmconnection + ] +} +`, context) +} diff --git a/website/docs/r/kms_ekm_connection.html.markdown b/website/docs/r/kms_ekm_connection.html.markdown new file mode 100644 index 0000000000..fb945ee2f1 --- /dev/null +++ b/website/docs/r/kms_ekm_connection.html.markdown @@ -0,0 +1,203 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Key Management Service" +description: |- + `Ekm Connections` are used to control the connection settings for an `EXTERNAL_VPC` CryptoKey. +--- + +# google\_kms\_ekm\_connection + +`Ekm Connections` are used to control the connection settings for an `EXTERNAL_VPC` CryptoKey. +It is used to connect customer's external key manager to Google Cloud EKM. + + +~> **Note:** Ekm Connections cannot be deleted from Google Cloud Platform. + + +To get more information about EkmConnection, see: + +* [API documentation](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.ekmConnections) +* How-to Guides + * [Creating a Ekm Connection](https://cloud.google.com/kms/docs/create-ekm-connection) + +## Example Usage - Kms Ekm Connection Basic + + +```hcl +resource "google_kms_ekm_connection" "example-ekmconnection" { + name = "ekmconnection_example" + location = "us-central1" + key_management_mode = "MANUAL" + service_resolvers { + service_directory_service = "projects/project_id/locations/us-central1/namespaces/namespace_name/services/service_name" + hostname = "example-ekm.goog" + server_certificates { + raw_der = "==HAwIBCCAr6gAwIBAgIUWR+EV4lqiV7Ql12VY==" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The resource name for the EkmConnection. + +* `service_resolvers` - + (Required) + A list of ServiceResolvers where the EKM can be reached. There should be one ServiceResolver per EKM replica. Currently, only a single ServiceResolver is supported + Structure is [documented below](#nested_service_resolvers). + +* `location` - + (Required) + The location for the EkmConnection. + A full list of valid locations can be found by running `gcloud kms locations list`. + + +The `service_resolvers` block supports: + +* `service_directory_service` - + (Required) + Required. The resource name of the Service Directory service pointing to an EKM replica, in the format projects/*/locations/*/namespaces/*/services/* + +* `hostname` - + (Required) + Required. The hostname of the EKM replica used at TLS and HTTP layers. + +* `server_certificates` - + (Required) + Required. A list of leaf server certificates used to authenticate HTTPS connections to the EKM replica. Currently, a maximum of 10 Certificate is supported. + Structure is [documented below](#nested_server_certificates). + +* `endpoint_filter` - + (Optional) + Optional. The filter applied to the endpoints of the resolved service. If no filter is specified, all endpoints will be considered. An endpoint will be chosen arbitrarily from the filtered list for each request. For endpoint filter syntax and examples, see https://cloud.google.com/service-directory/docs/reference/rpc/google.cloud.servicedirectory.v1#resolveservicerequest. + + +The `server_certificates` block supports: + +* `raw_der` - + (Required) + Required. The raw certificate bytes in DER format. A base64-encoded string. + +* `parsed` - + (Output) + Output only. True if the certificate was parsed successfully. + +* `issuer` - + (Output) + Output only. The issuer distinguished name in RFC 2253 format. Only present if parsed is true. + +* `subject` - + (Output) + Output only. The subject distinguished name in RFC 2253 format. Only present if parsed is true. + +* `not_before_time` - + (Output) + Output only. The certificate is not valid before this time. Only present if parsed is true. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z". + +* `not_after_time` - + (Output) + Output only. The certificate is not valid after this time. Only present if parsed is true. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z". + +* `sha256_fingerprint` - + (Output) + Output only. The SHA-256 certificate fingerprint as a hex string. Only present if parsed is true. + +* `serial_number` - + (Output) + Output only. The certificate serial number as a hex string. Only present if parsed is true. + +* `subject_alternative_dns_names` - + (Output) + Output only. The subject Alternative DNS names. Only present if parsed is true. + +- - - + + +* `key_management_mode` - + (Optional) + Optional. Describes who can perform control plane operations on the EKM. If unset, this defaults to MANUAL + Default value is `MANUAL`. + Possible values are: `MANUAL`, `CLOUD_KMS`. + +* `etag` - + (Optional) + Optional. Etag of the currently stored EkmConnection. + +* `crypto_space_path` - + (Optional) + Optional. Identifies the EKM Crypto Space that this EkmConnection maps to. Note: This field is required if KeyManagementMode is CLOUD_KMS. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}` + +* `create_time` - + Output only. The time at which the EkmConnection was created. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z". + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +EkmConnection can be imported using any of these accepted formats: + +* `projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}` +* `{{project}}/{{location}}/{{name}}` +* `{{location}}/{{name}}` + + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import EkmConnection using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/locations/{{location}}/ekmConnections/{{name}}" + to = google_kms_ekm_connection.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), EkmConnection can be imported using one of the formats above. For example: + +``` +$ terraform import google_kms_ekm_connection.default projects/{{project}}/locations/{{location}}/ekmConnections/{{name}} +$ terraform import google_kms_ekm_connection.default {{project}}/{{location}}/{{name}} +$ terraform import google_kms_ekm_connection.default {{location}}/{{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).