Skip to content

Commit

Permalink
Private CA - Create expander for X509Config (#5368)
Browse files Browse the repository at this point in the history
* Create custom expander for X509Config

In order to handle the case of optional primitive fields, I've
added virtual fields to allow the expander to distinguish
between an unset primitive and a primitive set to the default
value.

Since some Private CA resources do not support updates, I also
added support for setting ForceNew in the terraform config.

* Add error handling for expansion

This prevents incompatiable configs that set the allow* booleans
without setting basic constraints, and vice versa.

* Change virtual field names, and  other feedback

* Update examples with virtual fields

* Use url_param_only instead of virtual field.

* Handle CertificateAuthority resource which does not have field include_is_ca

* Fix format issue

* * Update description for fields `include_is_ca`
`include_max_issuer_path_path` to reflect its current functionality
* Add field `include_is_ca` to CertificateAuthority to avoding checking
the existence of this field in flattener.
* Update examples with new fields like `include_is_ca`, `include_max_issuer_path_length`.

* Update description; Add test cases for CaOption

* fix a typo

* remove include_x from template resource

* Update semantic meaning for newly added fields to avoid breaking
changes.

* User `nonCa`, `zeroMaxIssuerPathLength` instead of `includeIsCa`
`includeMaxIssuerPathLength`
* Update test cases.

* Create custom expander for X509Config

In order to handle the case of optional primitive fields, I've
added virtual fields to allow the expander to distinguish
between an unset primitive and a primitive set to the default
value.

Since some Private CA resources do not support updates, I also
added support for setting ForceNew in the terraform config.

* Add error handling for expansion

This prevents incompatiable configs that set the allow* booleans
without setting basic constraints, and vice versa.

* Change virtual field names, and  other feedback

* Update examples with virtual fields

* Use url_param_only instead of virtual field.

* Handle CertificateAuthority resource which does not have field include_is_ca

* Fix format issue

* * Update description for fields `include_is_ca`
`include_max_issuer_path_path` to reflect its current functionality
* Add field `include_is_ca` to CertificateAuthority to avoding checking
the existence of this field in flattener.
* Update examples with new fields like `include_is_ca`, `include_max_issuer_path_length`.

* Update description; Add test cases for CaOption

* fix a typo

* remove include_x from template resource

* Update semantic meaning for newly added fields to avoid breaking
changes.

* User `nonCa`, `zeroMaxIssuerPathLength` instead of `includeIsCa`
`includeMaxIssuerPathLength`
* Update test cases.

* Update doc-string for fields in CaOptions

Co-authored-by: Yong Cao <[email protected]>
  • Loading branch information
haydentherapper and gfxcc authored Dec 8, 2021
1 parent 9c021c0 commit 0fc925e
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 27 deletions.
63 changes: 48 additions & 15 deletions mmv1/products/privateca/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,25 @@ objects:
name: 'isCa'
required: true
description: |
Refers to the "CA" X.509 extension, which is a boolean value. When this value is missing,
the extension will be omitted from the CA certificate.
When true, the "CA" in Basic Constraints extension will be set to true.
- !ruby/object:Api::Type::Boolean
name: 'nonCa'
url_param_only: true
description: |
When true, the "CA" in Basic Constraints extension will be set to false.
If both `is_ca` and `non_ca` are unset, the extension will be omitted from the CA certificate.
- !ruby/object:Api::Type::Integer
name: 'maxIssuerPathLength'
description: |
Refers to the path length restriction X.509 extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail. If this
value is missing, the max path length will be omitted from the CA certificate.
Refers to the "path length constraint" in Basic Constraints extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail.
- !ruby/object:Api::Type::Boolean
name: 'zeroMaxIssuerPathLength'
url_param_only: true
description: |
When true, the "path length constraint" in Basic Constraints extension will be set to 0.
if both `max_issuer_path_length` and `zero_max_issuer_path_length` are unset,
the max path length will be omitted from the CA certificate.
- !ruby/object:Api::Type::NestedObject
name: 'keyUsage'
required: true
Expand Down Expand Up @@ -985,14 +996,25 @@ objects:
- !ruby/object:Api::Type::Boolean
name: 'isCa'
description: |
Refers to the "CA" X.509 extension, which is a boolean value. When this value is missing,
the extension will be omitted from the CA certificate.
When true, the "CA" in Basic Constraints extension will be set to true.
- !ruby/object:Api::Type::Boolean
name: 'nonCa'
url_param_only: true
description: |
When true, the "CA" in Basic Constraints extension will be set to false.
If both `is_ca` and `non_ca` are unset, the extension will be omitted from the CA certificate.
- !ruby/object:Api::Type::Integer
name: 'maxIssuerPathLength'
description: |
Refers to the path length restriction X.509 extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail. If this
value is missing, the max path length will be omitted from the CA certificate.
Refers to the "path length constraint" in Basic Constraints extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail.
- !ruby/object:Api::Type::Boolean
name: 'zeroMaxIssuerPathLength'
url_param_only: true
description: |
When true, the "path length constraint" in Basic Constraints extension will be set to 0.
if both `max_issuer_path_length` and `zero_max_issuer_path_length` are unset,
the max path length will be omitted from the CA certificate.
- !ruby/object:Api::Type::NestedObject
name: 'keyUsage'
required: true
Expand Down Expand Up @@ -1412,14 +1434,25 @@ objects:
- !ruby/object:Api::Type::Boolean
name: 'isCa'
description: |
Refers to the "CA" X.509 extension, which is a boolean value. When this value is missing,
the extension will be omitted from the CA certificate.
When true, the "CA" in Basic Constraints extension will be set to true.
- !ruby/object:Api::Type::Boolean
name: 'nonCa'
url_param_only: true
description: |
When true, the "CA" in Basic Constraints extension will be set to false.
If both `is_ca` and `non_ca` are unset, the extension will be omitted from the CA certificate.
- !ruby/object:Api::Type::Integer
name: 'maxIssuerPathLength'
description: |
Refers to the path length restriction X.509 extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail. If this
value is missing, the max path length will be omitted from the CA certificate.
Refers to the "path length constraint" in Basic Constraints extension. For a CA certificate, this value describes the depth of
subordinate CA certificates that are allowed. If this value is less than 0, the request will fail.
- !ruby/object:Api::Type::Boolean
name: 'zeroMaxIssuerPathLength'
url_param_only: true
description: |
When true, the "path length constraint" in Basic Constraints extension will be set to 0.
if both `max_issuer_path_length` and `zero_max_issuer_path_length` are unset,
the max path length will be omitted from the CA certificate.
- !ruby/object:Api::Type::NestedObject
name: 'keyUsage'
required: true
Expand Down
7 changes: 3 additions & 4 deletions mmv1/products/privateca/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides
issue certificates.
config.x509Config: !ruby/object:Overrides::Terraform::PropertyOverride
custom_flatten: 'templates/terraform/custom_flatten/privateca_certificate_509_config.go.erb'
config.x509Config.caOptions.maxIssuerPathLength: !ruby/object:Overrides::Terraform::PropertyOverride
send_empty_value: true
custom_expand: 'templates/terraform/custom_expand/privateca_certificate_509_config.go.erb'
custom_code: !ruby/object:Provider::Terraform::CustomCode
decoder: templates/terraform/decoders/treat_deleted_state_as_gone.go.erb
post_create: templates/terraform/post_create/privateca_authority_enable.go.erb
Expand All @@ -84,6 +83,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides
properties:
config.x509Config: !ruby/object:Overrides::Terraform::PropertyOverride
custom_flatten: 'templates/terraform/custom_flatten/privateca_certificate_509_config.go.erb'
custom_expand: 'templates/terraform/custom_expand/privateca_certificate_509_config.go.erb'
certificateTemplate: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: 'compareResourceNames'
examples:
Expand Down Expand Up @@ -135,8 +135,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides
properties:
issuancePolicy.baselineValues: !ruby/object:Overrides::Terraform::PropertyOverride
custom_flatten: 'templates/terraform/custom_flatten/privateca_certificate_509_config.go.erb'
issuancePolicy.baselineValues.caOptions.maxIssuerPathLength: !ruby/object:Overrides::Terraform::PropertyOverride
send_empty_value: true
custom_expand: 'templates/terraform/custom_expand/privateca_certificate_509_config.go.erb'
publishingOptions: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: 'emptyOrUnsetBlockDiffSuppress'
iam_policy: !ruby/object:Api::Resource::IamPolicy
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<%# See mmv1/third_party/terraform/utils/privateca_utils.go for the sub-expanders and explanation %>
func expand<%= prefix -%><%= titlelize_property(property) -%>(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
if v == nil {
return v, nil
}
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
if len(original) == 0 {
return nil, nil
}
transformed := make(map[string]interface{})

caOptions, err := expandPrivatecaCertificateConfigX509ConfigCaOptions(original["ca_options"], d, config)
if err != nil {
return nil, err
}
transformed["caOptions"] = caOptions

keyUsage, err := expandPrivatecaCertificateConfigX509ConfigKeyUsage(original["key_usage"], d, config)
if err != nil {
return nil, err
}
transformed["keyUsage"] = keyUsage

policyIds, err := expandPrivatecaCertificateConfigX509ConfigPolicyIds(original["policy_ids"], d, config)
if err != nil {
return nil, err
}
transformed["policyIds"] = policyIds

aiaOcspServers, err := expandPrivatecaCertificateConfigX509ConfigAiaOcspServers(original["aia_ocsp_servers"], d, config)
if err != nil {
return nil, err
}
transformed["aiaOcspServers"] = aiaOcspServers

addExts, err := expandPrivatecaCertificateConfigX509ConfigAdditionalExtensions(original["additional_extensions"], d, config)
if err != nil {
return nil, err
}
transformed["additionalExtensions"] = addExts

return transformed, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id]
ca_options {
is_ca = true
# Force the sub CA to only issue leaf certs
zero_max_issuer_path_length = true
max_issuer_path_length = 0
}
key_usage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ resource "google_privateca_certificate" "<%= ctx[:primary_resource_id] %>" {
}
x509_config {
ca_options {
non_ca = true
is_ca = false
}
key_usage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ resource "google_privateca_certificate" "<%= ctx[:primary_resource_id] %>" {
}
x509_config {
ca_options {
non_ca = true
is_ca = false
}
key_usage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ resource "google_privateca_certificate_template" "template" {
aia_ocsp_servers = ["string"]

ca_options {
is_ca = false
max_issuer_path_length = 6
is_ca = false
max_issuer_path_length = 6
}

key_usage {
Expand Down
124 changes: 124 additions & 0 deletions mmv1/third_party/terraform/tests/resource_privateca_ca_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,127 @@ resource "google_privateca_ca_pool" "default" {
}
`, context)
}

func TestAccPrivatecaCaPool_updateCaOption(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"random_suffix": randString(t, 10),
}

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPrivatecaCaPoolDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccPrivatecaCaPool_privatecaCapoolCaOptionIsCaIsTrueAndMaxPathIsPositive(context),
},
{
ResourceName: "google_privateca_ca_pool.default",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"name", "location"},
},
{
Config: testAccPrivatecaCaPool_privatecaCapoolCaOptionIsCaIsFalse(context),
},
{
ResourceName: "google_privateca_ca_pool.default",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"name", "location"},
},
{
Config: testAccPrivatecaCaPool_privatecaCapoolCaOptionMaxIssuerPathLenghIsZero(context),
},
{
ResourceName: "google_privateca_ca_pool.default",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"name", "location"},
},
},
})
}

func testAccPrivatecaCaPool_privatecaCapoolCaOptionIsCaIsTrueAndMaxPathIsPositive(context map[string]interface{}) string {
return Nprintf(`
resource "google_privateca_ca_pool" "default" {
name = "tf-test-my-capool%{random_suffix}"
location = "us-central1"
tier = "ENTERPRISE"
issuance_policy {
baseline_values {
ca_options {
is_ca = true
max_issuer_path_length = 10
}
key_usage {
base_key_usage {
digital_signature = true
}
extended_key_usage {
server_auth = true
}
}
}
}
}
`, context)
}

func testAccPrivatecaCaPool_privatecaCapoolCaOptionIsCaIsFalse(context map[string]interface{}) string {
return Nprintf(`
resource "google_privateca_ca_pool" "default" {
name = "tf-test-my-capool%{random_suffix}"
location = "us-central1"
tier = "ENTERPRISE"
issuance_policy {
baseline_values {
ca_options {
non_ca = true
is_ca = false
}
key_usage {
base_key_usage {
digital_signature = true
}
extended_key_usage {
server_auth = true
}
}
}
}
}
`, context)
}

func testAccPrivatecaCaPool_privatecaCapoolCaOptionMaxIssuerPathLenghIsZero(context map[string]interface{}) string {
return Nprintf(`
resource "google_privateca_ca_pool" "default" {
name = "tf-test-my-capool%{random_suffix}"
location = "us-central1"
tier = "ENTERPRISE"
issuance_policy {
baseline_values {
ca_options {
zero_max_issuer_path_length = true
max_issuer_path_length = 0
}
key_usage {
base_key_usage {
digital_signature = true
}
extended_key_usage {
server_auth = true
}
}
}
}
}
`, context)
}
Loading

0 comments on commit 0fc925e

Please sign in to comment.