Skip to content

Commit

Permalink
Add certificate template field to privateca Certificate (#5135) (#9915)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
modular-magician authored Aug 25, 2021
1 parent 73b8b17 commit 84a5746
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/5135.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
`privateca`: Added `certificate_template` to `google_privateca_certificate`.
```
2 changes: 1 addition & 1 deletion google/resource_composer_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
28 changes: 28 additions & 0 deletions google/resource_privateca_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
153 changes: 153 additions & 0 deletions google/resource_privateca_certificate_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
134 changes: 134 additions & 0 deletions website/docs/r/privateca_certificate.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 84a5746

Please sign in to comment.