From 4b7dc8d6c77864f0be9b6c333baca6d298585d2a Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 19 Jan 2021 14:26:08 -0800 Subject: [PATCH] Misc improvements for Certificate Authority (#4407) (#2883) Signed-off-by: Modular Magician --- .changelog/4407.txt | 9 + .../iam_privateca_certificate_authority.go | 166 +++++++++++ ...ca_certificate_authority_generated_test.go | 271 ++++++++++++++++++ google-beta/provider.go | 7 +- ...esource_privateca_certificate_authority.go | 239 ++++++++++----- ...ca_certificate_authority_generated_test.go | 73 +++++ ...vateca_certificate_authority.html.markdown | 99 ++++++- ...ca_certificate_authority_iam.html.markdown | 145 ++++++++++ website/google.erb | 6 +- 9 files changed, 926 insertions(+), 89 deletions(-) create mode 100644 .changelog/4407.txt create mode 100644 google-beta/iam_privateca_certificate_authority.go create mode 100644 google-beta/iam_privateca_certificate_authority_generated_test.go create mode 100644 website/docs/r/privateca_certificate_authority_iam.html.markdown diff --git a/.changelog/4407.txt b/.changelog/4407.txt new file mode 100644 index 0000000000..17988d7539 --- /dev/null +++ b/.changelog/4407.txt @@ -0,0 +1,9 @@ +```release-note:new-resource +google_privateca_certificate_authority_iam_policy +``` +```release-note:new-resource +google_privateca_certificate_authority_iam_binding +``` +```release-note:new-resource +google_privateca_certificate_authority_iam_member +``` diff --git a/google-beta/iam_privateca_certificate_authority.go b/google-beta/iam_privateca_certificate_authority.go new file mode 100644 index 0000000000..b10976e2b2 --- /dev/null +++ b/google-beta/iam_privateca_certificate_authority.go @@ -0,0 +1,166 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- +package google + +import ( + "fmt" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var PrivatecaCertificateAuthorityIamSchema = map[string]*schema.Schema{ + "certificate_authority": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + }, +} + +type PrivatecaCertificateAuthorityIamUpdater struct { + certificateAuthority string + d *schema.ResourceData + Config *Config +} + +func PrivatecaCertificateAuthorityIamUpdaterProducer(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + values := make(map[string]string) + + if v, ok := d.GetOk("certificate_authority"); ok { + values["certificate_authority"] = v.(string) + } + + // We may have gotten either a long or short name, so attempt to parse long name if possible + m, err := getImportIdQualifiers([]string{"projects/(?P[^/]+)/locations/(?P[^/]+)/certificateAuthorities/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)"}, d, config, d.Get("certificate_authority").(string)) + if err != nil { + return nil, err + } + + for k, v := range m { + values[k] = v + } + + u := &PrivatecaCertificateAuthorityIamUpdater{ + certificateAuthority: values["certificate_authority"], + d: d, + Config: config, + } + + if err := d.Set("certificate_authority", u.GetResourceId()); err != nil { + return nil, fmt.Errorf("Error setting certificate_authority: %s", err) + } + + return u, nil +} + +func PrivatecaCertificateAuthorityIdParseFunc(d *schema.ResourceData, config *Config) error { + values := make(map[string]string) + + m, err := getImportIdQualifiers([]string{"projects/(?P[^/]+)/locations/(?P[^/]+)/certificateAuthorities/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)"}, d, config, d.Id()) + if err != nil { + return err + } + + for k, v := range m { + values[k] = v + } + + u := &PrivatecaCertificateAuthorityIamUpdater{ + certificateAuthority: values["certificate_authority"], + d: d, + Config: config, + } + if err := d.Set("certificate_authority", u.GetResourceId()); err != nil { + return fmt.Errorf("Error setting certificate_authority: %s", err) + } + d.SetId(u.GetResourceId()) + return nil +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + url, err := u.qualifyCertificateAuthorityUrl("getIamPolicy") + if err != nil { + return nil, err + } + + var obj map[string]interface{} + + userAgent, err := generateUserAgentString(u.d, u.Config.userAgent) + if err != nil { + return nil, err + } + + policy, err := sendRequest(u.Config, "GET", "", url, userAgent, obj) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + out := &cloudresourcemanager.Policy{} + err = Convert(policy, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err) + } + + return out, nil +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + json, err := ConvertToMap(policy) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + obj["policy"] = json + + url, err := u.qualifyCertificateAuthorityUrl("setIamPolicy") + if err != nil { + return err + } + + userAgent, err := generateUserAgentString(u.d, u.Config.userAgent) + if err != nil { + return err + } + + _, err = sendRequestWithTimeout(u.Config, "POST", "", url, userAgent, obj, u.d.Timeout(schema.TimeoutCreate)) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) qualifyCertificateAuthorityUrl(methodIdentifier string) (string, error) { + urlTemplate := fmt.Sprintf("{{PrivatecaBasePath}}%s:%s", fmt.Sprintf("%s", u.certificateAuthority), methodIdentifier) + url, err := replaceVars(u.d, u.Config, urlTemplate) + if err != nil { + return "", err + } + return url, nil +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) GetResourceId() string { + return fmt.Sprintf("%s", u.certificateAuthority) +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-privateca-certificateauthority-%s", u.GetResourceId()) +} + +func (u *PrivatecaCertificateAuthorityIamUpdater) DescribeResource() string { + return fmt.Sprintf("privateca certificateauthority %q", u.GetResourceId()) +} diff --git a/google-beta/iam_privateca_certificate_authority_generated_test.go b/google-beta/iam_privateca_certificate_authority_generated_test.go new file mode 100644 index 0000000000..ac962dd708 --- /dev/null +++ b/google-beta/iam_privateca_certificate_authority_generated_test.go @@ -0,0 +1,271 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccPrivatecaCertificateAuthorityIamBindingGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "role": "roles/privateca.certificateManager", + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + Steps: []resource.TestStep{ + { + Config: testAccPrivatecaCertificateAuthorityIamBinding_basicGenerated(context), + }, + { + // Test Iam Binding update + Config: testAccPrivatecaCertificateAuthorityIamBinding_updateGenerated(context), + }, + }, + }) +} + +func TestAccPrivatecaCertificateAuthorityIamMemberGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "role": "roles/privateca.certificateManager", + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccPrivatecaCertificateAuthorityIamMember_basicGenerated(context), + }, + }, + }) +} + +func TestAccPrivatecaCertificateAuthorityIamPolicyGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "role": "roles/privateca.certificateManager", + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + Steps: []resource.TestStep{ + { + Config: testAccPrivatecaCertificateAuthorityIamPolicy_basicGenerated(context), + }, + { + Config: testAccPrivatecaCertificateAuthorityIamPolicy_emptyBinding(context), + }, + }, + }) +} + +func testAccPrivatecaCertificateAuthorityIamMember_basicGenerated(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + 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"] + } + } + reusable_config { + reusable_config = "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + disable_on_delete = true +} + +resource "google_privateca_certificate_authority_iam_member" "foo" { + provider = google-beta + certificate_authority = google_privateca_certificate_authority.default.id + role = "%{role}" + member = "user:admin@hashicorptest.com" +} +`, context) +} + +func testAccPrivatecaCertificateAuthorityIamPolicy_basicGenerated(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + 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"] + } + } + reusable_config { + reusable_config = "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + disable_on_delete = true +} + +data "google_iam_policy" "foo" { + provider = google-beta + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + } +} + +resource "google_privateca_certificate_authority_iam_policy" "foo" { + provider = google-beta + certificate_authority = google_privateca_certificate_authority.default.id + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAccPrivatecaCertificateAuthorityIamPolicy_emptyBinding(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + 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"] + } + } + reusable_config { + reusable_config = "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + disable_on_delete = true +} + +data "google_iam_policy" "foo" { + provider = google-beta +} + +resource "google_privateca_certificate_authority_iam_policy" "foo" { + provider = google-beta + certificate_authority = google_privateca_certificate_authority.default.id + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAccPrivatecaCertificateAuthorityIamBinding_basicGenerated(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + 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"] + } + } + reusable_config { + reusable_config = "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + disable_on_delete = true +} + +resource "google_privateca_certificate_authority_iam_binding" "foo" { + provider = google-beta + certificate_authority = google_privateca_certificate_authority.default.id + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} +`, context) +} + +func testAccPrivatecaCertificateAuthorityIamBinding_updateGenerated(context map[string]interface{}) string { + return Nprintf(` +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + 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"] + } + } + reusable_config { + reusable_config = "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } + key_spec { + algorithm = "RSA_PKCS1_4096_SHA256" + } + disable_on_delete = true +} + +resource "google_privateca_certificate_authority_iam_binding" "foo" { + provider = google-beta + certificate_authority = google_privateca_certificate_authority.default.id + role = "%{role}" + members = ["user:admin@hashicorptest.com", "user:paddy@hashicorp.com"] +} +`, context) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index 14d31e74b4..75faaaf762 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -799,8 +799,8 @@ func Provider() *schema.Provider { } // Generated resources: 207 -// Generated IAM resources: 105 -// Total generated resources: 312 +// Generated IAM resources: 108 +// Total generated resources: 315 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1072,6 +1072,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_os_config_guest_policies": resourceOSConfigGuestPolicies(), "google_os_login_ssh_public_key": resourceOSLoginSSHPublicKey(), "google_privateca_certificate_authority": resourcePrivatecaCertificateAuthority(), + "google_privateca_certificate_authority_iam_binding": ResourceIamBinding(PrivatecaCertificateAuthorityIamSchema, PrivatecaCertificateAuthorityIamUpdaterProducer, PrivatecaCertificateAuthorityIdParseFunc), + "google_privateca_certificate_authority_iam_member": ResourceIamMember(PrivatecaCertificateAuthorityIamSchema, PrivatecaCertificateAuthorityIamUpdaterProducer, PrivatecaCertificateAuthorityIdParseFunc), + "google_privateca_certificate_authority_iam_policy": ResourceIamPolicy(PrivatecaCertificateAuthorityIamSchema, PrivatecaCertificateAuthorityIamUpdaterProducer, PrivatecaCertificateAuthorityIdParseFunc), "google_pubsub_topic": resourcePubsubTopic(), "google_pubsub_topic_iam_binding": ResourceIamBinding(PubsubTopicIamSchema, PubsubTopicIamUpdaterProducer, PubsubTopicIdParseFunc), "google_pubsub_topic_iam_member": ResourceIamMember(PubsubTopicIamSchema, PubsubTopicIamUpdaterProducer, PubsubTopicIdParseFunc), diff --git a/google-beta/resource_privateca_certificate_authority.go b/google-beta/resource_privateca_certificate_authority.go index d753b73fbb..a835c65b5c 100644 --- a/google-beta/resource_privateca_certificate_authority.go +++ b/google-beta/resource_privateca_certificate_authority.go @@ -18,12 +18,25 @@ import ( "fmt" "log" "reflect" + "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +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 +} + func resourcePrivatecaCertificateAuthority() *schema.Resource { return &schema.Resource{ Create: resourcePrivatecaCertificateAuthorityCreate, @@ -46,7 +59,7 @@ func resourcePrivatecaCertificateAuthority() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - Description: `GCP region of the Realm.`, + Description: `The user provided Resource ID for this Certificate Authority.`, }, "config": { Type: schema.TypeList, @@ -64,10 +77,14 @@ func resourcePrivatecaCertificateAuthority() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "reusable_config": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: certificateAuthorityReusableConfigDiffSuppress, Description: `A resource path to a ReusableConfig in the format -projects/*/locations/*/reusableConfigs/*.`, +'projects/*/locations/*/reusableConfigs/*'. +. Alternatively, one of the short names +found by running 'gcloud beta privateca reusable-configs list'.`, }, }, }, @@ -79,6 +96,11 @@ projects/*/locations/*/reusableConfigs/*.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "common_name": { + Type: schema.TypeString, + Required: true, + Description: `The common name of the distinguished name.`, + }, "subject": { Type: schema.TypeList, Required: true, @@ -86,49 +108,51 @@ projects/*/locations/*/reusableConfigs/*.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "organization": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The organization of the subject.`, + }, "country_code": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The country code of the subject.`, }, "locality": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The locality or city of the subject.`, }, - "organization": { - Type: schema.TypeString, - Optional: true, - Description: `The organization of the subject.`, - }, "organizational_unit": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The organizational unit of the subject.`, }, "postal_code": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The postal code of the subject.`, }, "province": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The province, territory, or regional state of the subject.`, }, "street_address": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: `The street address of the subject.`, }, }, }, }, - "common_name": { - Type: schema.TypeString, - Optional: true, - Description: `The common name of the distinguished name.`, - }, "subject_alt_name": { Type: schema.TypeList, Optional: true, @@ -139,34 +163,42 @@ projects/*/locations/*/reusableConfigs/*.`, "dns_names": { Type: schema.TypeList, Optional: true, + ForceNew: true, Description: `Contains only valid, fully-qualified host names.`, Elem: &schema.Schema{ Type: schema.TypeString, }, + AtLeastOneOf: []string{"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"}, }, "email_addresses": { Type: schema.TypeList, Optional: true, + ForceNew: true, Description: `Contains only valid RFC 2822 E-mail addresses.`, Elem: &schema.Schema{ Type: schema.TypeString, }, + AtLeastOneOf: []string{"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"}, }, "ip_addresses": { Type: schema.TypeList, Optional: true, + ForceNew: true, Description: `Contains only valid 32-bit IPv4 addresses or RFC 4291 IPv6 addresses.`, Elem: &schema.Schema{ Type: schema.TypeString, }, + AtLeastOneOf: []string{"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"}, }, "uris": { Type: schema.TypeList, Optional: true, + ForceNew: true, Description: `Contains only valid RFC 3986 URIs.`, Elem: &schema.Schema{ Type: schema.TypeString, }, + AtLeastOneOf: []string{"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"}, }, }, }, @@ -189,18 +221,28 @@ certificate. Otherwise, it is used to sign a CSR.`, Schema: map[string]*schema.Schema{ "algorithm": { Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"SIGN_HASH_ALGORITHM_UNSPECIFIED", "RSA_PSS_2048_SHA256", "RSA_PSS_3072_SHA256", "RSA_PSS_4096_SHA256", "RSA_PKCS1_2048_SHA256", "RSA_PKCS1_3072_SHA256", "RSA_PKCS1_4096_SHA256", "EC_P256_SHA256", "EC_P384_SHA384"}, false), + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"SIGN_HASH_ALGORITHM_UNSPECIFIED", "RSA_PSS_2048_SHA256", "RSA_PSS_3072_SHA256", "RSA_PSS_4096_SHA256", "RSA_PKCS1_2048_SHA256", "RSA_PKCS1_3072_SHA256", "RSA_PKCS1_4096_SHA256", "EC_P256_SHA256", "EC_P384_SHA384", ""}, false), 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. Possible values: ["SIGN_HASH_ALGORITHM_UNSPECIFIED", "RSA_PSS_2048_SHA256", "RSA_PSS_3072_SHA256", "RSA_PSS_4096_SHA256", "RSA_PKCS1_2048_SHA256", "RSA_PKCS1_3072_SHA256", "RSA_PKCS1_4096_SHA256", "EC_P256_SHA256", "EC_P384_SHA384"]`, + ExactlyOneOf: []string{"key_spec.0.cloud_kms_key_version", "key_spec.0.algorithm"}, + }, + "cloud_kms_key_version": { + Type: schema.TypeString, + Optional: true, + Description: `The resource name for an existing Cloud KMS CryptoKeyVersion in the format +'projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*'.`, + ExactlyOneOf: []string{"key_spec.0.cloud_kms_key_version", "key_spec.0.algorithm"}, }, }, }, }, "location": { - Type: schema.TypeString, - Required: true, - Description: `Location of the Certificate Authority.`, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Location of the CertificateAuthority. A full list of valid locations can be found by +running 'gcloud beta privateca locations list'.`, }, "gcs_bucket": { Type: schema.TypeString, @@ -208,8 +250,8 @@ experience. All managed keys will be have their ProtectionLevel as HSM. Possible ForceNew: true, 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.`, }, "issuing_options": { @@ -249,6 +291,7 @@ An object containing a list of "key": value pairs. Example: { "name": "wrench", "lifetime": { Type: schema.TypeString, Optional: true, + ForceNew: true, 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".`, @@ -259,16 +302,22 @@ fractional digits, terminated by 's'. Example: "3.5s".`, Optional: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"ENTERPRISE", "DEVOPS", ""}, false), - Description: `The Tier of this CertificateAuthority. Default value: "ENTERPRISE" Possible values: ["ENTERPRISE", "DEVOPS"]`, - Default: "ENTERPRISE", + 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). Default value: "ENTERPRISE" Possible values: ["ENTERPRISE", "DEVOPS"]`, + Default: "ENTERPRISE", }, "type": { Type: schema.TypeString, Optional: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"SELF_SIGNED", ""}, false), - Description: `The Type of this CertificateAuthority. Default value: "SELF_SIGNED" Possible values: ["SELF_SIGNED"]`, - Default: "SELF_SIGNED", + ValidateFunc: validation.StringInSlice([]string{"SELF_SIGNED", "SUBORDINATE", ""}, false), + Description: `The Type of this CertificateAuthority. + +~> **Note:** For 'SUBORDINATE' Certificate Authorities, they need to +be manually activated (via Cloud Console of 'gcloud') before they can +issue certificates. Default value: "SELF_SIGNED" Possible values: ["SELF_SIGNED", "SUBORDINATE"]`, + Default: "SELF_SIGNED", }, "access_urls": { Type: schema.TypeList, @@ -329,6 +378,11 @@ CertificateAuthority's certificate.`, A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, }, + "disable_on_delete": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "project": { Type: schema.TypeString, Optional: true, @@ -397,6 +451,11 @@ func resourcePrivatecaCertificateAuthorityCreate(d *schema.ResourceData, meta in obj["labels"] = labelsProp } + obj, err = resourcePrivatecaCertificateAuthorityEncoder(d, meta, obj) + if err != nil { + return err + } + url, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/certificateAuthorities?certificateAuthorityId={{certificate_authority_id}}") if err != nil { return err @@ -486,6 +545,12 @@ func resourcePrivatecaCertificateAuthorityRead(d *schema.ResourceData, meta inte return handleNotFoundError(err, d, fmt.Sprintf("PrivatecaCertificateAuthority %q", d.Id())) } + // Explicitly set virtual fields to default values if unset + if _, ok := d.GetOkExists("disable_on_delete"); !ok { + if err := d.Set("disable_on_delete", false); err != nil { + return fmt.Errorf("Error setting disable_on_delete: %s", err) + } + } if err := d.Set("project", project); err != nil { return fmt.Errorf("Error reading CertificateAuthority: %s", err) } @@ -552,48 +617,12 @@ func resourcePrivatecaCertificateAuthorityUpdate(d *schema.ResourceData, meta in billingProject = project obj := make(map[string]interface{}) - typeProp, err := expandPrivatecaCertificateAuthorityType(d.Get("type"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, typeProp)) { - obj["type"] = typeProp - } - tierProp, err := expandPrivatecaCertificateAuthorityTier(d.Get("tier"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("tier"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, tierProp)) { - obj["tier"] = tierProp - } - configProp, err := expandPrivatecaCertificateAuthorityConfig(d.Get("config"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, configProp)) { - obj["config"] = configProp - } - lifetimeProp, err := expandPrivatecaCertificateAuthorityLifetime(d.Get("lifetime"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("lifetime"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, lifetimeProp)) { - obj["lifetime"] = lifetimeProp - } - keySpecProp, err := expandPrivatecaCertificateAuthorityKeySpec(d.Get("key_spec"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("key_spec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, keySpecProp)) { - obj["keySpec"] = keySpecProp - } issuingOptionsProp, err := expandPrivatecaCertificateAuthorityIssuingOptions(d.Get("issuing_options"), d, config) if err != nil { return err } else if v, ok := d.GetOkExists("issuing_options"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, issuingOptionsProp)) { obj["issuingOptions"] = issuingOptionsProp } - gcsBucketProp, err := expandPrivatecaCertificateAuthorityGcsBucket(d.Get("gcs_bucket"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("gcs_bucket"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, gcsBucketProp)) { - obj["gcsBucket"] = gcsBucketProp - } labelsProp, err := expandPrivatecaCertificateAuthorityLabels(d.Get("labels"), d, config) if err != nil { return err @@ -601,19 +630,39 @@ func resourcePrivatecaCertificateAuthorityUpdate(d *schema.ResourceData, meta in obj["labels"] = labelsProp } + obj, err = resourcePrivatecaCertificateAuthorityEncoder(d, meta, obj) + if err != nil { + return err + } + url, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}}") if err != nil { return err } log.Printf("[DEBUG] Updating CertificateAuthority %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("issuing_options") { + updateMask = append(updateMask, "issuingOptions") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } // err == nil indicates that the billing_project value was found if bp, err := getBillingProject(d, config); err == nil { billingProject = bp } - res, err := sendRequestWithTimeout(config, "PUT", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("Error updating CertificateAuthority %q: %s", d.Id(), err) @@ -653,21 +702,23 @@ func resourcePrivatecaCertificateAuthorityDelete(d *schema.ResourceData, meta in } var obj map[string]interface{} - 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 + } } log.Printf("[DEBUG] Deleting CertificateAuthority %q", d.Id()) @@ -710,6 +761,11 @@ func resourcePrivatecaCertificateAuthorityImport(d *schema.ResourceData, meta in } d.SetId(id) + // Explicitly set virtual fields to default values on import + if err := d.Set("disable_on_delete", false); err != nil { + return nil, fmt.Errorf("Error setting disable_on_delete: %s", err) + } + return []*schema.ResourceData{d}, nil } @@ -879,10 +935,16 @@ func flattenPrivatecaCertificateAuthorityKeySpec(v interface{}, d *schema.Resour return nil } transformed := make(map[string]interface{}) + transformed["cloud_kms_key_version"] = + flattenPrivatecaCertificateAuthorityKeySpecCloudKmsKeyVersion(original["cloudKmsKeyVersion"], d, config) transformed["algorithm"] = flattenPrivatecaCertificateAuthorityKeySpecAlgorithm(original["algorithm"], d, config) return []interface{}{transformed} } +func flattenPrivatecaCertificateAuthorityKeySpecCloudKmsKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenPrivatecaCertificateAuthorityKeySpecAlgorithm(v interface{}, d *schema.ResourceData, config *Config) interface{} { return v } @@ -1209,6 +1271,13 @@ func expandPrivatecaCertificateAuthorityKeySpec(v interface{}, d TerraformResour original := raw.(map[string]interface{}) transformed := make(map[string]interface{}) + transformedCloudKmsKeyVersion, err := expandPrivatecaCertificateAuthorityKeySpecCloudKmsKeyVersion(original["cloud_kms_key_version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCloudKmsKeyVersion); val.IsValid() && !isEmptyValue(val) { + transformed["cloudKmsKeyVersion"] = transformedCloudKmsKeyVersion + } + transformedAlgorithm, err := expandPrivatecaCertificateAuthorityKeySpecAlgorithm(original["algorithm"], d, config) if err != nil { return nil, err @@ -1219,6 +1288,10 @@ func expandPrivatecaCertificateAuthorityKeySpec(v interface{}, d TerraformResour return transformed, nil } +func expandPrivatecaCertificateAuthorityKeySpecCloudKmsKeyVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandPrivatecaCertificateAuthorityKeySpecAlgorithm(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } @@ -1271,3 +1344,19 @@ func expandPrivatecaCertificateAuthorityLabels(v interface{}, d TerraformResourc } return m, nil } + +func resourcePrivatecaCertificateAuthorityEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + 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/google-beta/resource_privateca_certificate_authority_generated_test.go b/google-beta/resource_privateca_certificate_authority_generated_test.go index e41f7a00b9..141619e34b 100644 --- a/google-beta/resource_privateca_certificate_authority_generated_test.go +++ b/google-beta/resource_privateca_certificate_authority_generated_test.go @@ -68,6 +68,7 @@ resource "google_privateca_certificate_authority" "default" { key_spec { algorithm = "RSA_PKCS1_4096_SHA256" } + disable_on_delete = true } `, context) } @@ -132,6 +133,78 @@ resource "google_privateca_certificate_authority" "default" { key_spec { algorithm = "EC_P256_SHA256" } + disable_on_delete = true +} +`, context) +} + +func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityCmekExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "kms_key_name": BootstrapKMSKeyWithPurposeInLocation(t, "ASYMMETRIC_SIGN", "us-central1").CryptoKey.Name, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": {}, + }, + CheckDestroy: testAccCheckPrivatecaCertificateAuthorityDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityCmekExample(context), + }, + }, + }) +} + +func testAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityCmekExample(context map[string]interface{}) string { + return Nprintf(` +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 = "%{kms_key_name}" + role = "roles/cloudkms.signerVerifier" + + members = [ + "serviceAccount:${google_project_service_identity.privateca_sa.email}", + ] +} + +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + certificate_authority_id = "tf-test%{random_suffix}" + location = "us-central1" + + key_spec { + cloud_kms_key_version = "%{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 } `, context) } diff --git a/website/docs/r/privateca_certificate_authority.html.markdown b/website/docs/r/privateca_certificate_authority.html.markdown index 203c3f85bf..bb6a245150 100644 --- a/website/docs/r/privateca_certificate_authority.html.markdown +++ b/website/docs/r/privateca_certificate_authority.html.markdown @@ -12,7 +12,7 @@ # .github/CONTRIBUTING.md. # # ---------------------------------------------------------------------------- -subcategory: "Certificate Authority" +subcategory: "Certificate Authority Service" layout: "google" page_title: "Google: google_privateca_certificate_authority" sidebar_current: "docs-google-privateca-certificate-authority" @@ -25,6 +25,11 @@ description: |- A CertificateAuthority represents an individual Certificate Authority. A CertificateAuthority can be used to create Certificates. + +~> **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. + ~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. @@ -64,6 +69,7 @@ resource "google_privateca_certificate_authority" "default" { key_spec { algorithm = "RSA_PKCS1_4096_SHA256" } + disable_on_delete = true } ```
@@ -111,6 +117,60 @@ resource "google_privateca_certificate_authority" "default" { key_spec { algorithm = "EC_P256_SHA256" } + disable_on_delete = true +} +``` + +## Example Usage - Privateca Certificate Authority Cmek + + +```hcl +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 = "projects/keys-project/locations/us-central1/keyRings/key-ring/cryptoKeys/crypto-key" + role = "roles/cloudkms.signerVerifier" + + members = [ + "serviceAccount:${google_project_service_identity.privateca_sa.email}", + ] +} + +resource "google_privateca_certificate_authority" "default" { + provider = google-beta + certificate_authority_id = "tf-test%{random_suffix}" + location = "us-central1" + + key_spec { + cloud_kms_key_version = "projects/keys-project/locations/us-central1/keyRings/key-ring/cryptoKeys/crypto-key/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 } ``` @@ -121,11 +181,12 @@ The following arguments are supported: * `location` - (Required) - Location of the Certificate Authority. + Location of the CertificateAuthority. A full list of valid locations can be found by + running `gcloud beta privateca locations list`. * `certificate_authority_id` - (Required) - GCP region of the Realm. + The user provided Resource ID for this Certificate Authority. * `config` - (Required) @@ -161,7 +222,7 @@ The `subject_config` block supports: Structure is documented below. * `common_name` - - (Optional) + (Required) The common name of the distinguished name. * `subject_alt_name` - @@ -177,7 +238,7 @@ The `subject` block supports: The country code of the subject. * `organization` - - (Optional) + (Required) The organization of the subject. * `organizational_unit` - @@ -223,12 +284,19 @@ The `reusable_config` block supports: * `reusable_config` - (Required) A resource path to a ReusableConfig in the format - projects/*/locations/*/reusableConfigs/*. + `projects/*/locations/*/reusableConfigs/*`. + . Alternatively, one of the short names + found by running `gcloud beta privateca reusable-configs list`. The `key_spec` block supports: +* `cloud_kms_key_version` - + (Optional) + The resource name for an existing Cloud KMS CryptoKeyVersion in the format + `projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*`. + * `algorithm` - - (Required) + (Optional) 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. Possible values are `SIGN_HASH_ALGORITHM_UNSPECIFIED`, `RSA_PSS_2048_SHA256`, `RSA_PSS_3072_SHA256`, `RSA_PSS_4096_SHA256`, `RSA_PKCS1_2048_SHA256`, `RSA_PKCS1_3072_SHA256`, `RSA_PKCS1_4096_SHA256`, `EC_P256_SHA256`, and `EC_P384_SHA384`. @@ -239,12 +307,17 @@ The `key_spec` block supports: * `type` - (Optional) The Type of this CertificateAuthority. + ~> **Note:** For `SUBORDINATE` Certificate Authorities, they need to + be manually activated (via Cloud Console of `gcloud`) before they can + issue certificates. Default value is `SELF_SIGNED`. - Possible values are `SELF_SIGNED`. + Possible values are `SELF_SIGNED` and `SUBORDINATE`. * `tier` - (Optional) - The Tier of this CertificateAuthority. + 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). Default value is `ENTERPRISE`. Possible values are `ENTERPRISE` and `DEVOPS`. @@ -263,8 +336,8 @@ The `key_spec` block supports: (Optional) 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. * `labels` - @@ -276,6 +349,10 @@ The `key_spec` block supports: * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. +* `disable_on_delete` - (Optional) 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`. + The `issuing_options` block supports: diff --git a/website/docs/r/privateca_certificate_authority_iam.html.markdown b/website/docs/r/privateca_certificate_authority_iam.html.markdown new file mode 100644 index 0000000000..3f4ff4ff36 --- /dev/null +++ b/website/docs/r/privateca_certificate_authority_iam.html.markdown @@ -0,0 +1,145 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Certificate Authority Service" +layout: "google" +page_title: "Google: google_privateca_certificate_authority_iam" +sidebar_current: "docs-google-privateca-certificate-authority-iam" +description: |- + Collection of resources to manage IAM policy for Certificate Authority Service CertificateAuthority +--- + +# IAM policy for Certificate Authority Service CertificateAuthority +Three different resources help you manage your IAM policy for Certificate Authority Service CertificateAuthority. Each of these resources serves a different use case: + +* `google_privateca_certificate_authority_iam_policy`: Authoritative. Sets the IAM policy for the certificateauthority and replaces any existing policy already attached. +* `google_privateca_certificate_authority_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the certificateauthority are preserved. +* `google_privateca_certificate_authority_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the certificateauthority are preserved. + +~> **Note:** `google_privateca_certificate_authority_iam_policy` **cannot** be used in conjunction with `google_privateca_certificate_authority_iam_binding` and `google_privateca_certificate_authority_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_privateca_certificate_authority_iam_binding` resources **can be** used in conjunction with `google_privateca_certificate_authority_iam_member` resources **only if** they do not grant privilege to the same role. + + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + + +## google\_privateca\_certificate\_authority\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/privateca.certificateManager" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_privateca_certificate_authority_iam_policy" "policy" { + certificate_authority = google_privateca_certificate_authority.default.id + policy_data = data.google_iam_policy.admin.policy_data +} +``` + +## google\_privateca\_certificate\_authority\_iam\_binding + +```hcl +resource "google_privateca_certificate_authority_iam_binding" "binding" { + certificate_authority = google_privateca_certificate_authority.default.id + role = "roles/privateca.certificateManager" + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_privateca\_certificate\_authority\_iam\_member + +```hcl +resource "google_privateca_certificate_authority_iam_member" "member" { + certificate_authority = google_privateca_certificate_authority.default.id + role = "roles/privateca.certificateManager" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `location` - (Required) Location of the CertificateAuthority. A full list of valid locations can be found by +running `gcloud beta privateca locations list`. + Used to find the parent resource to bind the IAM policy to + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the project will be parsed from the identifier of the parent resource. If no project is provided in the parent identifier and no project is specified, the provider project is used. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_privateca_certificate_authority_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_privateca_certificate_authority_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the IAM policy. + +## Import + +For all import syntaxes, the "resource in question" can take any of the following forms: + +* projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}} +* {{project}}/{{location}}/{{certificate_authority_id}} +* {{location}}/{{certificate_authority_id}} + +Any variables not passed in the import command will be taken from the provider configuration. + +Certificate Authority Service certificateauthority IAM resources can be imported using the resource identifiers, role, and member. + +IAM member imports use space-delimited identifiers: the resource in question, the role, and the member identity, e.g. +``` +$ terraform import google_privateca_certificate_authority_iam_member.editor "projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}} roles/privateca.certificateManager user:jane@example.com" +``` + +IAM binding imports use space-delimited identifiers: the resource in question and the role, e.g. +``` +$ terraform import google_privateca_certificate_authority_iam_binding.editor "projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}} roles/privateca.certificateManager" +``` + +IAM policy imports use the identifier of the resource in question, e.g. +``` +$ terraform import google_privateca_certificate_authority_iam_policy.editor projects/{{project}}/locations/{{location}}/certificateAuthorities/{{certificate_authority_id}} +``` + +-> **Custom Roles**: If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/google.erb b/website/google.erb index 2c130f9555..2a4bd1c06f 100644 --- a/website/google.erb +++ b/website/google.erb @@ -356,7 +356,7 @@
  • - Certificate Authority + Certificate Authority Service