diff --git a/products/privateca/api.yaml b/products/privateca/api.yaml index 77f8fb18c7bc..ad5832bf0df4 100644 --- a/products/privateca/api.yaml +++ b/products/privateca/api.yaml @@ -13,7 +13,7 @@ --- !ruby/object:Api::Product name: Privateca -display_name: Certificate Authority +display_name: Certificate Authority Service versions: - !ruby/object:Api::Product::Version name: beta @@ -53,6 +53,8 @@ objects: self_link: projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}} min_version: beta create_verb: :POST + update_verb: :PATCH + update_mask: true delete_url: '{{name}}:scheduleDelete' delete_verb: :POST references: !ruby/object:Api::Resource::ReferenceLinks @@ -63,12 +65,15 @@ objects: properties: - !ruby/object:Api::Type::String name: location - description: Location of the Certificate Authority. + description: | + Location of the CertificateAuthority. A full list of valid locations can be found by + running `gcloud beta privateca locations list`. required: true + input: true url_param_only: true - !ruby/object:Api::Type::String name: certificateAuthorityId - description: GCP region of the Realm. + description: The user provided Resource ID for this Certificate Authority. required: true input: true url_param_only: true @@ -84,10 +89,14 @@ objects: input: true values: - :SELF_SIGNED + - :SUBORDINATE default_value: :SELF_SIGNED - !ruby/object:Api::Type::Enum name: 'tier' - description: The Tier of this CertificateAuthority. + description: | + The Tier of this CertificateAuthority. `ENTERPRISE` Certificate Authorities track + server side certificates issued, and support certificate revocation. For more details, + please check the [associated documentation](https://cloud.google.com/certificate-authority-service/docs/tiers). input: true values: - :ENTERPRISE @@ -113,27 +122,36 @@ objects: - !ruby/object:Api::Type::String name: 'countryCode' description: The country code of the subject. + input: true - !ruby/object:Api::Type::String name: 'organization' description: The organization of the subject. + input: true + required: true - !ruby/object:Api::Type::String name: 'organizationalUnit' description: The organizational unit of the subject. + input: true - !ruby/object:Api::Type::String name: 'locality' description: The locality or city of the subject. + input: true - !ruby/object:Api::Type::String name: 'province' description: The province, territory, or regional state of the subject. + input: true - !ruby/object:Api::Type::String name: 'streetAddress' description: The street address of the subject. + input: true - !ruby/object:Api::Type::String name: 'postalCode' description: The postal code of the subject. + input: true - !ruby/object:Api::Type::String name: 'commonName' description: The common name of the distinguished name. + required: true - !ruby/object:Api::Type::NestedObject name: 'subjectAltName' description: The subject alternative name fields. @@ -142,18 +160,42 @@ objects: name: 'dnsNames' description: Contains only valid, fully-qualified host names. item_type: Api::Type::String + at_least_one_of: + - config.0.subject_config.0.subject_alt_name.0.dns_names + - config.0.subject_config.0.subject_alt_name.0.uris + - config.0.subject_config.0.subject_alt_name.0.email_addresses + - config.0.subject_config.0.subject_alt_name.0.ip_addresses + input: true - !ruby/object:Api::Type::Array name: 'uris' description: Contains only valid RFC 3986 URIs. item_type: Api::Type::String + at_least_one_of: + - config.0.subject_config.0.subject_alt_name.0.dns_names + - config.0.subject_config.0.subject_alt_name.0.uris + - config.0.subject_config.0.subject_alt_name.0.email_addresses + - config.0.subject_config.0.subject_alt_name.0.ip_addresses + input: true - !ruby/object:Api::Type::Array name: 'emailAddresses' description: Contains only valid RFC 2822 E-mail addresses. item_type: Api::Type::String + at_least_one_of: + - config.0.subject_config.0.subject_alt_name.0.dns_names + - config.0.subject_config.0.subject_alt_name.0.uris + - config.0.subject_config.0.subject_alt_name.0.email_addresses + - config.0.subject_config.0.subject_alt_name.0.ip_addresses + input: true - !ruby/object:Api::Type::Array name: 'ipAddresses' description: Contains only valid 32-bit IPv4 addresses or RFC 4291 IPv6 addresses. item_type: Api::Type::String + at_least_one_of: + - config.0.subject_config.0.subject_alt_name.0.dns_names + - config.0.subject_config.0.subject_alt_name.0.uris + - config.0.subject_config.0.subject_alt_name.0.email_addresses + - config.0.subject_config.0.subject_alt_name.0.ip_addresses + input: true - !ruby/object:Api::Type::NestedObject name: 'reusableConfig' description: | @@ -164,8 +206,9 @@ objects: name: 'reusableConfig' description: | A resource path to a ReusableConfig in the format - projects/*/locations/*/reusableConfigs/*. + `projects/*/locations/*/reusableConfigs/*`. required: true + input: true - !ruby/object:Api::Type::String name: 'lifetime' description: | @@ -173,6 +216,7 @@ objects: "notAfterTime" fields inside an X.509 certificate. A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". default_value: 315360000s # 10 years + input: true - !ruby/object:Api::Type::NestedObject name: 'keySpec' description: | @@ -182,12 +226,19 @@ objects: required: true input: true properties: + - !ruby/object:Api::Type::String + name: 'cloudKmsKeyVersion' + description: | + The resource name for an existing Cloud KMS CryptoKeyVersion in the format + `projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*`. + exactly_one_of: + - key_spec.0.cloud_kms_key_version + - key_spec.0.algorithm - !ruby/object:Api::Type::Enum name: 'algorithm' description: | The algorithm to use for creating a managed Cloud KMS key for a for a simplified experience. All managed keys will be have their ProtectionLevel as HSM. - required: true values: - :SIGN_HASH_ALGORITHM_UNSPECIFIED - :RSA_PSS_2048_SHA256 @@ -198,6 +249,9 @@ objects: - :RSA_PKCS1_4096_SHA256 - :EC_P256_SHA256 - :EC_P384_SHA384 + exactly_one_of: + - key_spec.0.cloud_kms_key_version + - key_spec.0.algorithm - !ruby/object:Api::Type::NestedObject name: 'issuingOptions' description: | @@ -240,8 +294,8 @@ objects: description: | The name of a Cloud Storage bucket where this CertificateAuthority will publish content, such as the CA certificate and CRLs. This must be a bucket name, without any prefixes - (such as gs://) or suffixes (such as .googleapis.com). For example, to use a bucket named - my-bucket, you would simply specify my-bucket. If not specified, a managed bucket will be + (such as `gs://`) or suffixes (such as `.googleapis.com`). For example, to use a bucket named + my-bucket, you would simply specify `my-bucket`. If not specified, a managed bucket will be created. input: true - !ruby/object:Api::Type::NestedObject diff --git a/products/privateca/terraform.yaml b/products/privateca/terraform.yaml index 3d6aba8db028..7a5e6d228614 100644 --- a/products/privateca/terraform.yaml +++ b/products/privateca/terraform.yaml @@ -15,6 +15,18 @@ overrides: !ruby/object:Overrides::ResourceOverrides CertificateAuthority: !ruby/object:Overrides::Terraform::ResourceOverride autogen_async: true + iam_policy: !ruby/object:Api::Resource::IamPolicy + parent_resource_attribute: certificate_authority + allowed_iam_role: 'roles/privateca.certificateManager' + method_name_separator: ':' + example_config_body: 'templates/terraform/iam/example_config_body/privateca_certificate_authority.tf.erb' + base_url: '{{name}}' + description: | + {{description}} + + ~> **Warning:** Please remember that all resources created during preview (via the terraform-provider-google-beta) + will be deleted when CA service transitions to General Availability (GA). Relying on these + certificate authorities for production traffic is discouraged. import_format: ["projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}}"] examples: - !ruby/object:Provider::Terraform::Examples @@ -29,6 +41,38 @@ overrides: !ruby/object:Overrides::ResourceOverrides primary_resource_id: "default" vars: certificate_authority_id: "my-certificate-authority" + - !ruby/object:Provider::Terraform::Examples + min_version: beta + name: "privateca_certificate_authority_cmek" + min_version: "beta" + primary_resource_id: "default" + vars: + kms_key_name: "projects/keys-project/locations/us-central1/keyRings/key-ring/cryptoKeys/crypto-key" + test_vars_overrides: + kms_key_name: 'BootstrapKMSKeyWithPurposeInLocation(t, "ASYMMETRIC_SIGN", "us-central1").CryptoKey.Name' + virtual_fields: + - !ruby/object:Api::Type::Boolean + name: 'disable_on_delete' + default_value: false + description: | + If set to `true`, the Certificate Authority will be disabled + on delete. If the Certitificate Authorities is not disabled, + it cannot be deleted. Use with care. Defaults to `false`. + properties: + type: !ruby/object:Overrides::Terraform::PropertyOverride + description: | + {{description}} + + ~> **Note:** For `SUBORDINATE` Certificate Authorities, they need to + be manually activated (via Cloud Console of `gcloud`) before they can + issue certificates. + config.reusableConfig.reusableConfig: !ruby/object:Overrides::Terraform::PropertyOverride + description: | + {{description}}. Alternatively, one of the short names + found by running `gcloud beta privateca reusable-configs list`. + diff_suppress_func: 'certificateAuthorityReusableConfigDiffSuppress' custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/certificate_authority.go.erb + encoder: templates/terraform/encoders/certificate_authority.go.erb pre_delete: templates/terraform/pre_delete/privateca_certificate_authority.go.erb test_check_destroy: templates/terraform/custom_check_destroy/privateca_certificate_authority.go.erb diff --git a/templates/terraform/constants/certificate_authority.go.erb b/templates/terraform/constants/certificate_authority.go.erb new file mode 100644 index 000000000000..f45d73c16c93 --- /dev/null +++ b/templates/terraform/constants/certificate_authority.go.erb @@ -0,0 +1,11 @@ +func certificateAuthorityReusableConfigDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + if old != "" && new != "" { + newParts := strings.Split(new, "/") + // If the new form is a short version, we just + // check if it matches the suffix of the old version + if len(newParts) == 1 { + return strings.HasSuffix(old, new) + } + } + return old == new +} diff --git a/templates/terraform/encoders/certificate_authority.go.erb b/templates/terraform/encoders/certificate_authority.go.erb new file mode 100644 index 000000000000..645012b24fbb --- /dev/null +++ b/templates/terraform/encoders/certificate_authority.go.erb @@ -0,0 +1,13 @@ +rc := d.Get("config.0.reusable_config.0.reusable_config").(string) + +parts := strings.Split(rc, "/") + +if len(parts) == 1 { + // If we have a short form: add the full path to the reusable-configs from + // the Google-managed project and the location of the CA. + config := obj["config"].(map[string]interface{}) + configReusableConfig := config["reusableConfig"].(map[string]interface{}) + configReusableConfig["reusableConfig"] = fmt.Sprintf("projects/568668481468/locations/%s/reusableConfigs/%s", d.Get("location"), parts[0]) +} + +return obj, nil diff --git a/templates/terraform/examples/privateca_certificate_authority_basic.tf.erb b/templates/terraform/examples/privateca_certificate_authority_basic.tf.erb index e76f0d223f9a..1166f861a5ca 100644 --- a/templates/terraform/examples/privateca_certificate_authority_basic.tf.erb +++ b/templates/terraform/examples/privateca_certificate_authority_basic.tf.erb @@ -19,4 +19,5 @@ resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id] key_spec { algorithm = "RSA_PKCS1_4096_SHA256" } -} \ No newline at end of file + disable_on_delete = true +} diff --git a/templates/terraform/examples/privateca_certificate_authority_cmek.tf.erb b/templates/terraform/examples/privateca_certificate_authority_cmek.tf.erb new file mode 100644 index 000000000000..1e72def4cb0a --- /dev/null +++ b/templates/terraform/examples/privateca_certificate_authority_cmek.tf.erb @@ -0,0 +1,43 @@ +resource "google_project_service_identity" "privateca_sa" { + provider = google-beta + service = "privateca.googleapis.com" +} + +resource "google_kms_crypto_key_iam_binding" "privateca_sa_keyuser" { + provider = google-beta + crypto_key_id = "<%= ctx[:vars]['kms_key_name'] %>" + role = "roles/cloudkms.signerVerifier" + + members = [ + "serviceAccount:${google_project_service_identity.privateca_sa.email}", + ] +} + +resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + certificate_authority_id = "tf-test%{random_suffix}" + location = "us-central1" + + key_spec { + cloud_kms_key_version = "<%= ctx[:vars]['kms_key_name'] %>/cryptoKeyVersions/1" + } + + config { + subject_config { + common_name = "Example Authority" + subject { + organization = "Example, Org." + } + } + + reusable_config { + reusable_config= "root-unconstrained" + } + } + + depends_on = [ + google_kms_crypto_key_iam_binding.privateca_sa_keyuser, + ] + + disable_on_delete = true +} \ No newline at end of file diff --git a/templates/terraform/examples/privateca_certificate_authority_full.tf.erb b/templates/terraform/examples/privateca_certificate_authority_full.tf.erb index 3be0a912a732..64258aa5a42e 100644 --- a/templates/terraform/examples/privateca_certificate_authority_full.tf.erb +++ b/templates/terraform/examples/privateca_certificate_authority_full.tf.erb @@ -34,4 +34,5 @@ resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id] key_spec { algorithm = "EC_P256_SHA256" } -} \ No newline at end of file + disable_on_delete = true +} diff --git a/templates/terraform/iam/example_config_body/privateca_certificate_authority.tf.erb b/templates/terraform/iam/example_config_body/privateca_certificate_authority.tf.erb new file mode 100644 index 000000000000..92bc4454e2aa --- /dev/null +++ b/templates/terraform/iam/example_config_body/privateca_certificate_authority.tf.erb @@ -0,0 +1 @@ + certificate_authority = google_privateca_certificate_authority.default.id diff --git a/templates/terraform/pre_delete/privateca_certificate_authority.go.erb b/templates/terraform/pre_delete/privateca_certificate_authority.go.erb index 36078604a24a..ccb01d203f14 100644 --- a/templates/terraform/pre_delete/privateca_certificate_authority.go.erb +++ b/templates/terraform/pre_delete/privateca_certificate_authority.go.erb @@ -1,17 +1,19 @@ -log.Printf("[DEBUG] Disabling CertificateAuthority %q", d.Id()) +if d.Get("disable_on_delete").(bool) && d.Get("state").(string) == "ENABLED" { + log.Printf("[DEBUG] Disabling CertificateAuthority %q", d.Id()) -disableURL, err := replaceVars(d, config, "{{PrivatecaBasePath}}{{name}}:disable") -if err != nil { - return err -} + disableURL, err := replaceVars(d, config, "{{PrivatecaBasePath}}{{name}}:disable") + if err != nil { + return err + } -disableRes, err := sendRequestWithTimeout(config, "POST", billingProject, disableURL, userAgent, obj, d.Timeout(schema.TimeoutDelete)) -if err != nil { - return err -} + disableRes, err := sendRequestWithTimeout(config, "POST", billingProject, disableURL, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } -err = privatecaOperationWaitTime(config, disableRes, project, "Disabling CertificateAuthority", userAgent, d.Timeout(schema.TimeoutDelete)) -if err != nil { - return err + err = privatecaOperationWaitTime(config, disableRes, project, "Disabling CertificateAuthority", userAgent, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } }