diff --git a/.changelog/5055.txt b/.changelog/5055.txt new file mode 100644 index 0000000000..b01e0bc1c4 --- /dev/null +++ b/.changelog/5055.txt @@ -0,0 +1,3 @@ +```release-note:bug +privateca: fixed a failure to create `google_privateca_certificate_authority` of type `SUBORDINATE` due to an invalid attempt to activate it on creation. +``` diff --git a/google-beta/bootstrap_utils_test.go b/google-beta/bootstrap_utils_test.go index 64b12dcff2..b73d2b3b0d 100644 --- a/google-beta/bootstrap_utils_test.go +++ b/google-beta/bootstrap_utils_test.go @@ -429,3 +429,42 @@ func BootstrapSharedSQLInstanceBackupRun(t *testing.T) string { return bootstrapInstance.Name } + +func BootstrapSharedCaPoolInLocation(t *testing.T, location string) string { + project := getTestProjectFromEnv() + poolName := "static-ca-pool" + + config := BootstrapConfig(t) + if config == nil { + return "" + } + + log.Printf("[DEBUG] Getting shared CA pool %q", poolName) + url := fmt.Sprintf("%sprojects/%s/locations/%s/caPools/%s", config.PrivatecaBasePath, project, location, poolName) + _, err := sendRequest(config, "GET", project, url, config.userAgent, nil) + if err != nil { + log.Printf("[DEBUG] CA pool %q not found, bootstrapping", poolName) + poolObj := map[string]interface{}{ + "tier": "ENTERPRISE", + } + createUrl := fmt.Sprintf("%sprojects/%s/locations/%s/caPools?caPoolId=%s", config.PrivatecaBasePath, project, location, poolName) + res, err := sendRequestWithTimeout(config, "POST", project, createUrl, config.userAgent, poolObj, 4*time.Minute) + if err != nil { + t.Fatalf("Error bootstrapping shared CA pool %q: %s", poolName, err) + } + + log.Printf("[DEBUG] Waiting for CA pool creation to finish") + var opRes map[string]interface{} + err = privatecaOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating CA pool", config.userAgent, + 4*time.Minute) + if err != nil { + t.Errorf("Error getting shared CA pool %q: %s", poolName, err) + } + res, err = sendRequest(config, "GET", project, url, config.userAgent, nil) + if err != nil { + t.Errorf("Error getting shared CA pool %q: %s", poolName, err) + } + } + return poolName +} diff --git a/google-beta/resource_gke_hub_feature_membership_test.go b/google-beta/resource_gke_hub_feature_membership_test.go index 9e1f2ea64d..32fa84c196 100644 --- a/google-beta/resource_gke_hub_feature_membership_test.go +++ b/google-beta/resource_gke_hub_feature_membership_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" + dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" gkehub "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/gkehub/beta" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" diff --git a/google-beta/resource_privateca_certificate_authority.go b/google-beta/resource_privateca_certificate_authority.go index 895ee7f9cc..63ea6d29fb 100644 --- a/google-beta/resource_privateca_certificate_authority.go +++ b/google-beta/resource_privateca_certificate_authority.go @@ -698,23 +698,25 @@ func resourcePrivatecaCertificateAuthorityCreate(d *schema.ResourceData, meta in } d.SetId(id) - url, err = replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:enable") - if err != nil { - return err - } + if d.Get("type").(string) != "SUBORDINATE" { + url, err = replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:enable") + if err != nil { + return err + } - log.Printf("[DEBUG] Enabling CertificateAuthority: %#v", obj) + log.Printf("[DEBUG] Enabling CertificateAuthority: %#v", obj) - res, err = sendRequest(config, "POST", billingProject, url, userAgent, nil) - if err != nil { - return fmt.Errorf("Error enabling CertificateAuthority: %s", err) - } + res, err = sendRequest(config, "POST", billingProject, url, userAgent, nil) + if err != nil { + return fmt.Errorf("Error enabling CertificateAuthority: %s", err) + } - err = privatecaOperationWaitTimeWithResponse( - config, res, &opRes, project, "Enabling CertificateAuthority", userAgent, - d.Timeout(schema.TimeoutCreate)) - if err != nil { - return fmt.Errorf("Error waiting to enable CertificateAuthority: %s", err) + err = privatecaOperationWaitTimeWithResponse( + config, res, &opRes, project, "Enabling CertificateAuthority", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error waiting to enable CertificateAuthority: %s", err) + } } log.Printf("[DEBUG] Finished creating CertificateAuthority %q: %#v", d.Id(), res) @@ -829,24 +831,26 @@ func resourcePrivatecaCertificateAuthorityDelete(d *schema.ResourceData, meta in } var obj map[string]interface{} - disableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:disable") - if err != nil { - return err - } + if d.Get("state").(string) == "ENABLED" { + disableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:disable") + if err != nil { + return err + } - log.Printf("[DEBUG] Disabling CertificateAuthority: %#v", obj) + log.Printf("[DEBUG] Disabling CertificateAuthority: %#v", obj) - dRes, err := sendRequest(config, "POST", billingProject, disableUrl, userAgent, nil) - if err != nil { - return fmt.Errorf("Error disabling CertificateAuthority: %s", err) - } + dRes, err := sendRequest(config, "POST", billingProject, disableUrl, userAgent, nil) + if err != nil { + return fmt.Errorf("Error disabling CertificateAuthority: %s", err) + } - var opRes map[string]interface{} - err = privatecaOperationWaitTimeWithResponse( - config, dRes, &opRes, project, "Disabling CertificateAuthority", userAgent, - d.Timeout(schema.TimeoutDelete)) - if err != nil { - return fmt.Errorf("Error waiting to disable CertificateAuthority: %s", err) + var opRes map[string]interface{} + err = privatecaOperationWaitTimeWithResponse( + config, dRes, &opRes, project, "Disabling CertificateAuthority", userAgent, + d.Timeout(schema.TimeoutDelete)) + if err != nil { + return fmt.Errorf("Error waiting to disable CertificateAuthority: %s", err) + } } log.Printf("[DEBUG] Deleting CertificateAuthority %q", d.Id()) diff --git a/google-beta/resource_privateca_certificate_authority_generated_test.go b/google-beta/resource_privateca_certificate_authority_generated_test.go index 54ed503f1f..a1348e206b 100644 --- a/google-beta/resource_privateca_certificate_authority_generated_test.go +++ b/google-beta/resource_privateca_certificate_authority_generated_test.go @@ -27,7 +27,8 @@ func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityBasicExam t.Parallel() context := map[string]interface{}{ - "pool": "static-ca-pool", + "pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"), + "pool_location": "us-central1", "random_suffix": randString(t, 10), } @@ -54,9 +55,9 @@ func testAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityBasicExam resource "google_privateca_certificate_authority" "default" { // This example assumes this pool already exists. // Pools cannot be deleted in normal test circumstances, so we depend on static pools - pool = "%{pool}" + pool = "%{pool_name}" certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}" - location = "us-central1" + location = "%{pool_location}" config { subject_config { subject { @@ -101,13 +102,94 @@ resource "google_privateca_certificate_authority" "default" { `, context) } +func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"), + "pool_location": "us-central1", + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPrivatecaCertificateAuthorityDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(context), + }, + { + ResourceName: "google_privateca_certificate_authority.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ignore_active_certificates_on_deletion", "location", "certificate_authority_id", "pool"}, + }, + }, + }) +} + +func testAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + // This example assumes this pool already exists. + // Pools cannot be deleted in normal test circumstances, so we depend on static pools + pool = "%{pool_name}" + certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}" + location = "%{pool_location}" + config { + subject_config { + subject { + organization = "HashiCorp" + common_name = "my-subordinate-authority" + } + subject_alt_name { + dns_names = ["hashicorp.com"] + } + } + x509_config { + ca_options { + is_ca = true + max_issuer_path_length = 10 + } + key_usage { + base_key_usage { + digital_signature = true + content_commitment = true + key_encipherment = false + data_encipherment = true + key_agreement = true + cert_sign = true + crl_sign = true + decipher_only = true + } + extended_key_usage { + server_auth = true + client_auth = false + email_protection = true + code_signing = true + time_stamping = true + } + } + } + } + lifetime = "86400s" + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + type = "SUBORDINATE" +} +`, context) +} + func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityByoKeyExample(t *testing.T) { skipIfVcr(t) t.Parallel() context := map[string]interface{}{ "kms_key_name": BootstrapKMSKeyWithPurposeInLocation(t, "ASYMMETRIC_SIGN", "us-central1").CryptoKey.Name, - "pool": "static-ca-pool", + "pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"), + "pool_location": "us-central1", "random_suffix": randString(t, 10), } @@ -155,9 +237,9 @@ resource "google_kms_crypto_key_iam_binding" "privateca_sa_keyuser_viewer" { resource "google_privateca_certificate_authority" "default" { // This example assumes this pool already exists. // Pools cannot be deleted in normal test circumstances, so we depend on static pools - pool = "%{pool}" + pool = "%{pool_name}" certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}" - location = "us-central1" + location = "%{pool_location}" key_spec { cloud_kms_key_version = "%{kms_key_name}/cryptoKeyVersions/1" } diff --git a/website/docs/r/privateca_certificate_authority.html.markdown b/website/docs/r/privateca_certificate_authority.html.markdown index 985ec3f23b..65eea49008 100644 --- a/website/docs/r/privateca_certificate_authority.html.markdown +++ b/website/docs/r/privateca_certificate_authority.html.markdown @@ -44,7 +44,7 @@ To get more information about CertificateAuthority, see: resource "google_privateca_certificate_authority" "default" { // This example assumes this pool already exists. // Pools cannot be deleted in normal test circumstances, so we depend on static pools - pool = "" + pool = "ca-pool" certificate_authority_id = "my-certificate-authority" location = "us-central1" config { @@ -89,6 +89,64 @@ resource "google_privateca_certificate_authority" "default" { } } ``` +
+## Example Usage - Privateca Certificate Authority Subordinate + + +```hcl +resource "google_privateca_certificate_authority" "default" { + // This example assumes this pool already exists. + // Pools cannot be deleted in normal test circumstances, so we depend on static pools + pool = "ca-pool" + certificate_authority_id = "my-certificate-authority" + location = "us-central1" + config { + subject_config { + subject { + organization = "HashiCorp" + common_name = "my-subordinate-authority" + } + subject_alt_name { + dns_names = ["hashicorp.com"] + } + } + x509_config { + ca_options { + is_ca = true + max_issuer_path_length = 10 + } + key_usage { + base_key_usage { + digital_signature = true + content_commitment = true + key_encipherment = false + data_encipherment = true + key_agreement = true + cert_sign = true + crl_sign = true + decipher_only = true + } + extended_key_usage { + server_auth = true + client_auth = false + email_protection = true + code_signing = true + time_stamping = true + } + } + } + } + lifetime = "86400s" + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + type = "SUBORDINATE" +} +```