From 84a5746c6a7f291d4615559c90a7aa0f777481bc Mon Sep 17 00:00:00 2001 From: The Magician Date: Wed, 25 Aug 2021 17:45:27 -0500 Subject: [PATCH] Add certificate template field to privateca Certificate (#5135) (#9915) * Add certificate authority field to Certificate, now that certificate authority is present in TF. Also adds test - which will only successfully run in CI since it depends on a static CA pool. * compare resource names instead, because we have a project number. Signed-off-by: Modular Magician --- .changelog/5135.txt | 3 + google/resource_composer_environment.go | 2 +- google/resource_privateca_certificate.go | 28 ++++ ...ce_privateca_certificate_generated_test.go | 153 ++++++++++++++++++ .../r/privateca_certificate.html.markdown | 134 +++++++++++++++ 5 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 .changelog/5135.txt diff --git a/.changelog/5135.txt b/.changelog/5135.txt new file mode 100644 index 00000000000..b8aa4eb5eb5 --- /dev/null +++ b/.changelog/5135.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +`privateca`: Added `certificate_template` to `google_privateca_certificate`. +``` diff --git a/google/resource_composer_environment.go b/google/resource_composer_environment.go index 7a9d37a9e3a..fcee2760325 100644 --- a/google/resource_composer_environment.go +++ b/google/resource_composer_environment.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - composer "google.golang.org/api/composer/v1beta1" + "google.golang.org/api/composer/v1beta1" ) const ( diff --git a/google/resource_privateca_certificate.go b/google/resource_privateca_certificate.go index d055ec4ad2c..a33a0f595dd 100644 --- a/google/resource_privateca_certificate.go +++ b/google/resource_privateca_certificate.go @@ -59,6 +59,17 @@ running 'gcloud privateca locations list'.`, ForceNew: true, Description: `The name of the CaPool this Certificate belongs to.`, }, + "certificate_template": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: compareResourceNames, + Description: `The resource name for a CertificateTemplate used to issue this certificate, +in the format 'projects/*/locations/*/certificateTemplates/*'. If this is specified, +the caller must have the necessary permission to use this template. If this is +omitted, no template will be used. This template must be in the same location +as the Certificate.`, + }, "certificate_authority": { Type: schema.TypeString, Optional: true, @@ -953,6 +964,12 @@ func resourcePrivatecaCertificateCreate(d *schema.ResourceData, meta interface{} } else if v, ok := d.GetOkExists("lifetime"); !isEmptyValue(reflect.ValueOf(lifetimeProp)) && (ok || !reflect.DeepEqual(v, lifetimeProp)) { obj["lifetime"] = lifetimeProp } + certificateTemplateProp, err := expandPrivatecaCertificateCertificateTemplate(d.Get("certificate_template"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("certificate_template"); !isEmptyValue(reflect.ValueOf(certificateTemplateProp)) && (ok || !reflect.DeepEqual(v, certificateTemplateProp)) { + obj["certificateTemplate"] = certificateTemplateProp + } labelsProp, err := expandPrivatecaCertificateLabels(d.Get("labels"), d, config) if err != nil { return err @@ -1070,6 +1087,9 @@ func resourcePrivatecaCertificateRead(d *schema.ResourceData, meta interface{}) if err := d.Set("update_time", flattenPrivatecaCertificateUpdateTime(res["updateTime"], d, config)); err != nil { return fmt.Errorf("Error reading Certificate: %s", err) } + if err := d.Set("certificate_template", flattenPrivatecaCertificateCertificateTemplate(res["certificateTemplate"], d, config)); err != nil { + return fmt.Errorf("Error reading Certificate: %s", err) + } if err := d.Set("labels", flattenPrivatecaCertificateLabels(res["labels"], d, config)); err != nil { return fmt.Errorf("Error reading Certificate: %s", err) } @@ -1662,6 +1682,10 @@ func flattenPrivatecaCertificateUpdateTime(v interface{}, d *schema.ResourceData return v } +func flattenPrivatecaCertificateCertificateTemplate(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenPrivatecaCertificateLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { return v } @@ -1842,6 +1866,10 @@ func expandPrivatecaCertificateLifetime(v interface{}, d TerraformResourceData, return v, nil } +func expandPrivatecaCertificateCertificateTemplate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandPrivatecaCertificateLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil diff --git a/google/resource_privateca_certificate_generated_test.go b/google/resource_privateca_certificate_generated_test.go index bb8b5011501..eb968d85c14 100644 --- a/google/resource_privateca_certificate_generated_test.go +++ b/google/resource_privateca_certificate_generated_test.go @@ -133,6 +133,159 @@ resource "google_privateca_certificate" "default" { `, context) } +func TestAccPrivatecaCertificate_privatecaCertificateWithTemplateExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": getTestProjectFromEnv(), + "pool": "static-ca-pool", + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPrivatecaCertificateDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccPrivatecaCertificate_privatecaCertificateWithTemplateExample(context), + }, + { + ResourceName: "google_privateca_certificate.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"pool", "name", "location", "certificate_authority"}, + }, + }, + }) +} + +func testAccPrivatecaCertificate_privatecaCertificateWithTemplateExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_template" "template" { + location = "us-central1" + name = "tf-test-my-certificate-template%{random_suffix}" + description = "An updated sample certificate template" + + identity_constraints { + allow_subject_alt_names_passthrough = true + allow_subject_passthrough = true + + cel_expression { + description = "Always true" + expression = "true" + location = "any.file.anywhere" + title = "Sample expression" + } + } + + passthrough_extensions { + additional_extensions { + object_id_path = [1, 6] + } + + known_extensions = ["EXTENDED_KEY_USAGE"] + } + + predefined_values { + additional_extensions { + object_id { + object_id_path = [1, 6] + } + + value = "c3RyaW5nCg==" + critical = true + } + + aia_ocsp_servers = ["string"] + + ca_options { + is_ca = false + max_issuer_path_length = 6 + } + + key_usage { + base_key_usage { + cert_sign = false + content_commitment = true + crl_sign = false + data_encipherment = true + decipher_only = true + digital_signature = true + encipher_only = true + key_agreement = true + key_encipherment = true + } + + extended_key_usage { + client_auth = true + code_signing = true + email_protection = true + ocsp_signing = true + server_auth = true + time_stamping = true + } + + unknown_extended_key_usages { + object_id_path = [1, 6] + } + } + + policy_ids { + object_id_path = [1, 6] + } + } +} + +resource "google_privateca_certificate_authority" "test-ca" { + pool = "%{pool}" + certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}" + location = "us-central1" + config { + subject_config { + subject { + organization = "HashiCorp" + common_name = "my-certificate-authority" + } + subject_alt_name { + dns_names = ["hashicorp.com"] + } + } + x509_config { + ca_options { + # is_ca *MUST* be true for certificate authorities + is_ca = true + } + key_usage { + base_key_usage { + # cert_sign and crl_sign *MUST* be true for certificate authorities + cert_sign = true + crl_sign = true + } + extended_key_usage { + server_auth = false + } + } + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } +} + + +resource "google_privateca_certificate" "default" { + pool = "%{pool}" + location = "us-central1" + certificate_authority = google_privateca_certificate_authority.test-ca.certificate_authority_id + lifetime = "860s" + name = "tf-test-my-certificate%{random_suffix}" + pem_csr = file("test-fixtures/rsa_csr.pem") + certificate_template = google_privateca_certificate_template.template.id +} +`, context) +} + func TestAccPrivatecaCertificate_privatecaCertificateCsrExample(t *testing.T) { t.Parallel() diff --git a/website/docs/r/privateca_certificate.html.markdown b/website/docs/r/privateca_certificate.html.markdown index 74d135b91c9..cd38eb608b2 100644 --- a/website/docs/r/privateca_certificate.html.markdown +++ b/website/docs/r/privateca_certificate.html.markdown @@ -113,6 +113,132 @@ resource "google_privateca_certificate" "default" { } } ``` +## Example Usage - Privateca Certificate With Template + + +```hcl +resource "google_privateca_certificate_template" "template" { + location = "us-central1" + name = "my-certificate-template" + description = "An updated sample certificate template" + + identity_constraints { + allow_subject_alt_names_passthrough = true + allow_subject_passthrough = true + + cel_expression { + description = "Always true" + expression = "true" + location = "any.file.anywhere" + title = "Sample expression" + } + } + + passthrough_extensions { + additional_extensions { + object_id_path = [1, 6] + } + + known_extensions = ["EXTENDED_KEY_USAGE"] + } + + predefined_values { + additional_extensions { + object_id { + object_id_path = [1, 6] + } + + value = "c3RyaW5nCg==" + critical = true + } + + aia_ocsp_servers = ["string"] + + ca_options { + is_ca = false + max_issuer_path_length = 6 + } + + key_usage { + base_key_usage { + cert_sign = false + content_commitment = true + crl_sign = false + data_encipherment = true + decipher_only = true + digital_signature = true + encipher_only = true + key_agreement = true + key_encipherment = true + } + + extended_key_usage { + client_auth = true + code_signing = true + email_protection = true + ocsp_signing = true + server_auth = true + time_stamping = true + } + + unknown_extended_key_usages { + object_id_path = [1, 6] + } + } + + policy_ids { + object_id_path = [1, 6] + } + } +} + +resource "google_privateca_certificate_authority" "test-ca" { + pool = "" + certificate_authority_id = "my-certificate-authority" + location = "us-central1" + config { + subject_config { + subject { + organization = "HashiCorp" + common_name = "my-certificate-authority" + } + subject_alt_name { + dns_names = ["hashicorp.com"] + } + } + x509_config { + ca_options { + # is_ca *MUST* be true for certificate authorities + is_ca = true + } + key_usage { + base_key_usage { + # cert_sign and crl_sign *MUST* be true for certificate authorities + cert_sign = true + crl_sign = true + } + extended_key_usage { + server_auth = false + } + } + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } +} + + +resource "google_privateca_certificate" "default" { + pool = "" + location = "us-central1" + certificate_authority = google_privateca_certificate_authority.test-ca.certificate_authority_id + lifetime = "860s" + name = "my-certificate" + pem_csr = file("test-fixtures/rsa_csr.pem") + certificate_template = google_privateca_certificate_template.template.id +} +``` ## Example Usage - Privateca Certificate Csr @@ -276,6 +402,14 @@ The following arguments are supported: "notAfterTime" fields inside an X.509 certificate. A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". +* `certificate_template` - + (Optional) + The resource name for a CertificateTemplate used to issue this certificate, + in the format `projects/*/locations/*/certificateTemplates/*`. If this is specified, + the caller must have the necessary permission to use this template. If this is + omitted, no template will be used. This template must be in the same location + as the Certificate. + * `labels` - (Optional) Labels with user-defined metadata to apply to this resource.