Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc improvements for Certificate Authority #4407

Merged
merged 2 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 62 additions & 8 deletions products/privateca/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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: |
Expand All @@ -164,15 +206,17 @@ 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: |
The desired lifetime of the CA certificate. Used to create the "notBeforeTime" and
"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: |
Expand All @@ -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
Expand All @@ -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: |
Expand Down Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions products/privateca/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
11 changes: 11 additions & 0 deletions templates/terraform/constants/certificate_authority.go.erb
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions templates/terraform/encoders/certificate_authority.go.erb
Original file line number Diff line number Diff line change
@@ -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])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to verify: this needs to be set to a fixed project number?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. The API takes both a project number or project ID (the corresponding project ID for that project number is privateca-data), however the read call will always answer with the project number. So the point of passing the project number directly (instead of project ID) was to avoid a permadiff. But now that we have this custom encoder and the corresponding diff suppress function, we could pass the project ID itself since the diff suppress would only look into the suffix (the reusable config itself) when the user specifies only the short form. Would you prefer to have the project ID on the create call to make this more legible?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I've seen that behavior in other APIs as well. I think it is fine as is, especially when the comment above clarifies the Google-managed project part. Thanks for explaining.

}

return obj, nil
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id]
key_spec {
algorithm = "RSA_PKCS1_4096_SHA256"
}
}
disable_on_delete = true
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ resource "google_privateca_certificate_authority" "<%= ctx[:primary_resource_id]
key_spec {
algorithm = "EC_P256_SHA256"
}
}
disable_on_delete = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
certificate_authority = google_privateca_certificate_authority.default.id
Original file line number Diff line number Diff line change
@@ -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
}
}