diff --git a/.changelog/5865.txt b/.changelog/5865.txt new file mode 100644 index 00000000000..bec34918823 --- /dev/null +++ b/.changelog/5865.txt @@ -0,0 +1,12 @@ +```release-note:enhancement +access approval: added `active_key_version`, `ancestor_has_active_key_version`, and `invalid_key_version` fields to `google_folder_access_approval_settings`, `google_organization_access_approval_settings`, and `google_project_access_approval_settings` resources +``` +```release-note:new-datasource +`google_access_approval_folder_service_account` +``` +```release-note:new-datasource +`google_access_approval_organization_service_account` +``` +```release-note:new-datasource +`google_access_approval_project_service_account` +``` diff --git a/google/data_source_access_approval_folder_service_account.go b/google/data_source_access_approval_folder_service_account.go new file mode 100644 index 00000000000..8d258e9b083 --- /dev/null +++ b/google/data_source_access_approval_folder_service_account.go @@ -0,0 +1,62 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAccessApprovalFolderServiceAccount() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAccessApprovalFolderServiceAccountRead, + Schema: map[string]*schema.Schema{ + "folder_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "account_email": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAccessApprovalFolderServiceAccountRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}folders/{{folder_id}}/serviceAccount") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("AccessApprovalFolderServiceAccount %q", d.Id())) + } + + if err := d.Set("name", res["name"]); err != nil { + return fmt.Errorf("Error setting name: %s", err) + } + if err := d.Set("account_email", res["accountEmail"]); err != nil { + return fmt.Errorf("Error setting account_email: %s", err) + } + d.SetId(res["name"].(string)) + + return nil +} diff --git a/google/data_source_access_approval_folder_service_account_test.go b/google/data_source_access_approval_folder_service_account_test.go new file mode 100644 index 00000000000..0e39b9a7164 --- /dev/null +++ b/google/data_source_access_approval_folder_service_account_test.go @@ -0,0 +1,56 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAccessApprovalFolderServiceAccount_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "random_suffix": randString(t, 10), + } + + resourceName := "data.google_access_approval_folder_service_account.aa_account" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAccessApprovalFolderServiceAccount_basic(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "account_email"), + ), + }, + }, + }) +} + +func testAccDataSourceAccessApprovalFolderServiceAccount_basic(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "my_folder" { + display_name = "tf-test-my-folder%{random_suffix}" + parent = "organizations/%{org_id}" +} + +# Wait after folder creation to limit eventual consistency errors. +resource "time_sleep" "wait_120_seconds" { + depends_on = [google_folder.my_folder] + + create_duration = "120s" +} + +data "google_access_approval_folder_service_account" "aa_account" { + folder_id = google_folder.my_folder.folder_id + + depends_on = [time_sleep.wait_120_seconds] +} +`, context) +} diff --git a/google/data_source_access_approval_organization_service_account.go b/google/data_source_access_approval_organization_service_account.go new file mode 100644 index 00000000000..b7991fa952a --- /dev/null +++ b/google/data_source_access_approval_organization_service_account.go @@ -0,0 +1,62 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAccessApprovalOrganizationServiceAccount() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAccessApprovalOrganizationServiceAccountRead, + Schema: map[string]*schema.Schema{ + "organization_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "account_email": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAccessApprovalOrganizationServiceAccountRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}organizations/{{organization_id}}/serviceAccount") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("AccessApprovalOrganizationServiceAccount %q", d.Id())) + } + + if err := d.Set("name", res["name"]); err != nil { + return fmt.Errorf("Error setting name: %s", err) + } + if err := d.Set("account_email", res["accountEmail"]); err != nil { + return fmt.Errorf("Error setting account_email: %s", err) + } + d.SetId(res["name"].(string)) + + return nil +} diff --git a/google/data_source_access_approval_organization_service_account_test.go b/google/data_source_access_approval_organization_service_account_test.go new file mode 100644 index 00000000000..11756d25bb2 --- /dev/null +++ b/google/data_source_access_approval_organization_service_account_test.go @@ -0,0 +1,38 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAccessApprovalOrganizationServiceAccount_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + } + + resourceName := "data.google_access_approval_organization_service_account.aa_account" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAccessApprovalOrganizationServiceAccount_basic(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "account_email"), + ), + }, + }, + }) +} + +func testAccDataSourceAccessApprovalOrganizationServiceAccount_basic(context map[string]interface{}) string { + return Nprintf(` +data "google_access_approval_organization_service_account" "aa_account" { + organization_id = "%{org_id}" +} +`, context) +} diff --git a/google/data_source_access_approval_project_service_account.go b/google/data_source_access_approval_project_service_account.go new file mode 100644 index 00000000000..ee4865218eb --- /dev/null +++ b/google/data_source_access_approval_project_service_account.go @@ -0,0 +1,62 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAccessApprovalProjectServiceAccount() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAccessApprovalProjectServiceAccountRead, + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "account_email": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAccessApprovalProjectServiceAccountRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}projects/{{project_id}}/serviceAccount") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("AccessApprovalProjectServiceAccount %q", d.Id())) + } + + if err := d.Set("name", res["name"]); err != nil { + return fmt.Errorf("Error setting name: %s", err) + } + if err := d.Set("account_email", res["accountEmail"]); err != nil { + return fmt.Errorf("Error setting account_email: %s", err) + } + d.SetId(res["name"].(string)) + + return nil +} diff --git a/google/data_source_access_approval_project_service_account_test.go b/google/data_source_access_approval_project_service_account_test.go new file mode 100644 index 00000000000..e0713a91009 --- /dev/null +++ b/google/data_source_access_approval_project_service_account_test.go @@ -0,0 +1,38 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAccessApprovalProjectServiceAccount_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project_id": getTestProjectFromEnv(), + } + + resourceName := "data.google_access_approval_project_service_account.aa_account" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAccessApprovalProjectServiceAccount_basic(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "account_email"), + ), + }, + }, + }) +} + +func testAccDataSourceAccessApprovalProjectServiceAccount_basic(context map[string]interface{}) string { + return Nprintf(` +data "google_access_approval_project_service_account" "aa_account" { + project_id = "%{project_id}" +} +`, context) +} diff --git a/google/provider.go b/google/provider.go index 88e9cf2b016..1f99ae7ce65 100644 --- a/google/provider.go +++ b/google/provider.go @@ -717,6 +717,9 @@ func Provider() *schema.Provider { DataSourcesMap: map[string]*schema.Resource{ // ####### START datasources ########### + "google_access_approval_folder_service_account": dataSourceAccessApprovalFolderServiceAccount(), + "google_access_approval_organization_service_account": dataSourceAccessApprovalOrganizationServiceAccount(), + "google_access_approval_project_service_account": dataSourceAccessApprovalProjectServiceAccount(), "google_active_folder": dataSourceGoogleActiveFolder(), "google_app_engine_default_service_account": dataSourceGoogleAppEngineDefaultServiceAccount(), "google_billing_account": dataSourceGoogleBillingAccount(), diff --git a/google/resource_access_approval_folder_settings_test.go b/google/resource_access_approval_folder_settings_test.go index bd9cfacf4f3..f1d11f5fe2c 100644 --- a/google/resource_access_approval_folder_settings_test.go +++ b/google/resource_access_approval_folder_settings_test.go @@ -14,13 +14,18 @@ import ( // See AccessApprovalOrganizationSettings for the test runner. func testAccAccessApprovalFolderSettings(t *testing.T) { context := map[string]interface{}{ + "project": getTestProjectFromEnv(), "org_id": getTestOrgFromEnv(t), + "location": getTestRegionFromEnv(), "random_suffix": randString(t, 10), } vcrTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, CheckDestroy: testAccCheckAccessApprovalFolderSettingsDestroyProducer(t), Steps: []resource.TestStep{ { @@ -41,6 +46,15 @@ func testAccAccessApprovalFolderSettings(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"folder_id"}, }, + { + Config: testAccAccessApprovalFolderSettings_activeKeyVersion(context), + }, + { + ResourceName: "google_folder_access_approval_settings.folder_access_approval", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"folder_id"}, + }, }, }) } @@ -52,6 +66,13 @@ resource "google_folder" "my_folder" { parent = "organizations/%{org_id}" } +# Wait after folder creation to limit eventual consistency errors. +resource "time_sleep" "wait_120_seconds" { + depends_on = [google_folder.my_folder] + + create_duration = "120s" +} + resource "google_folder_access_approval_settings" "folder_access_approval" { folder_id = google_folder.my_folder.folder_id notification_emails = ["testuser@example.com"] @@ -59,6 +80,8 @@ resource "google_folder_access_approval_settings" "folder_access_approval" { enrolled_services { cloud_product = "all" } + + depends_on = [time_sleep.wait_120_seconds] } `, context) } @@ -70,6 +93,13 @@ resource "google_folder" "my_folder" { parent = "organizations/%{org_id}" } +# Wait after folder creation to limit eventual consistency errors. +resource "time_sleep" "wait_120_seconds" { + depends_on = [google_folder.my_folder] + + create_duration = "120s" +} + resource "google_folder_access_approval_settings" "folder_access_approval" { folder_id = google_folder.my_folder.folder_id notification_emails = ["testuser@example.com", "example.user@example.com"] @@ -77,6 +107,68 @@ resource "google_folder_access_approval_settings" "folder_access_approval" { enrolled_services { cloud_product = "all" } + + depends_on = [time_sleep.wait_120_seconds] +} +`, context) +} + +func testAccAccessApprovalFolderSettings_activeKeyVersion(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "my_folder" { + display_name = "tf-test-my-folder%{random_suffix}" + parent = "organizations/%{org_id}" +} + +# Wait after folder creation to limit eventual consistency errors. +resource "time_sleep" "wait_120_seconds" { + depends_on = [google_folder.my_folder] + + create_duration = "120s" +} + +resource "google_kms_key_ring" "key_ring" { + name = "tf-test-%{random_suffix}" + project = "%{project}" + location = "%{location}" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "tf-test-%{random_suffix}" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_folder_service_account" "aa_account" { + folder_id = google_folder.my_folder.folder_id + + depends_on = [time_sleep.wait_120_seconds] +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_folder_service_account.aa_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_folder_access_approval_settings" "folder_access_approval" { + folder_id = google_folder.my_folder.folder_id + + enrolled_services { + cloud_product = "all" + } + + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + depends_on = [google_kms_crypto_key_iam_member.iam] } `, context) } diff --git a/google/resource_access_approval_organization_settings_test.go b/google/resource_access_approval_organization_settings_test.go index aac0111c586..57b7a90c574 100644 --- a/google/resource_access_approval_organization_settings_test.go +++ b/google/resource_access_approval_organization_settings_test.go @@ -32,7 +32,9 @@ func TestAccAccessApprovalSettings(t *testing.T) { func testAccAccessApprovalOrganizationSettings(t *testing.T) { context := map[string]interface{}{ + "project": getTestProjectFromEnv(), "org_id": getTestOrgFromEnv(t), + "location": getTestRegionFromEnv(), "random_suffix": randString(t, 10), } @@ -59,6 +61,15 @@ func testAccAccessApprovalOrganizationSettings(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"organization_id"}, }, + { + Config: testAccAccessApprovalOrganizationSettings_activeKeyVersion(context), + }, + { + ResourceName: "google_organization_access_approval_settings.organization_access_approval", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization_id"}, + }, }, }) } @@ -95,6 +106,52 @@ resource "google_organization_access_approval_settings" "organization_access_app `, context) } +func testAccAccessApprovalOrganizationSettings_activeKeyVersion(context map[string]interface{}) string { + return Nprintf(` +resource "google_kms_key_ring" "key_ring" { + name = "tf-test-%{random_suffix}" + project = "%{project}" + location = "%{location}" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "tf-test-%{random_suffix}" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_organization_service_account" "aa_account" { + organization_id = "%{org_id}" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_organization_service_account.aa_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_organization_access_approval_settings" "organization_access_approval" { + organization_id = "%{org_id}" + + enrolled_services { + cloud_product = "all" + } + + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + depends_on = [google_kms_crypto_key_iam_member.iam] +} +`, context) +} + func testAccCheckAccessApprovalOrganizationSettingsDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google/resource_access_approval_project_settings_test.go b/google/resource_access_approval_project_settings_test.go index f95512ba4f3..64e5ff51d40 100644 --- a/google/resource_access_approval_project_settings_test.go +++ b/google/resource_access_approval_project_settings_test.go @@ -16,6 +16,7 @@ func testAccAccessApprovalProjectSettings(t *testing.T) { context := map[string]interface{}{ "project": getTestProjectFromEnv(), "org_id": getTestOrgFromEnv(t), + "location": getTestRegionFromEnv(), "random_suffix": randString(t, 10), } @@ -42,6 +43,15 @@ func testAccAccessApprovalProjectSettings(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"project_id"}, }, + { + Config: testAccAccessApprovalProjectSettings_activeKeyVersion(context), + }, + { + ResourceName: "google_project_access_approval_settings.project_access_approval", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project_id"}, + }, }, }) } @@ -73,6 +83,52 @@ resource "google_project_access_approval_settings" "project_access_approval" { `, context) } +func testAccAccessApprovalProjectSettings_activeKeyVersion(context map[string]interface{}) string { + return Nprintf(` +resource "google_kms_key_ring" "key_ring" { + name = "tf-test-%{random_suffix}" + project = "%{project}" + location = "%{location}" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "tf-test-%{random_suffix}" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_project_service_account" "aa_account" { + project_id = "%{project}" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_project_service_account.aa_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_project_access_approval_settings" "project_access_approval" { + project_id = "%{project}" + + enrolled_services { + cloud_product = "all" + } + + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + depends_on = [google_kms_crypto_key_iam_member.iam] +} +`, context) +} + func testAccCheckAccessApprovalProjectSettingsDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google/resource_folder_access_approval_settings.go b/google/resource_folder_access_approval_settings.go index c17cb5f019e..46dcb70360a 100644 --- a/google/resource_folder_access_approval_settings.go +++ b/google/resource_folder_access_approval_settings.go @@ -84,6 +84,13 @@ A maximum of 10 enrolled services will be enforced, to be expanded as the set of ForceNew: true, Description: `ID of the folder of the access approval settings.`, }, + "active_key_version": { + Type: schema.TypeString, + Optional: true, + Description: `The asymmetric crypto key version to use for signing approval requests. +Empty active_key_version indicates that a Google-managed key should be used for signing. +This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set.`, + }, "notification_emails": { Type: schema.TypeSet, Computed: true, @@ -97,11 +104,24 @@ resources of that resource. A maximum of 50 email addresses are allowed.`, }, Set: schema.HashString, }, + "ancestor_has_active_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `If the field is true, that indicates that an ancestor of this Folder has set active_key_version.`, + }, "enrolled_ancestor": { Type: schema.TypeBool, Computed: true, Description: `If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Folder.`, }, + "invalid_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `If the field is true, that indicates that there is some configuration issue with the active_key_version +configured on this Folder (e.g. it doesn't exist or the Access Approval service account doesn't have the +correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, +as key versions are inherited top-down.`, + }, "name": { Type: schema.TypeString, Computed: true, @@ -174,6 +194,12 @@ func resourceAccessApprovalFolderSettingsCreate(d *schema.ResourceData, meta int } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(enrolledServicesProp)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalFolderSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(activeKeyVersionProp)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}folders/{{folder_id}}/accessApprovalSettings") if err != nil { @@ -197,6 +223,10 @@ func resourceAccessApprovalFolderSettingsCreate(d *schema.ResourceData, meta int if d.HasChange("enrolled_services") { updateMask = append(updateMask, "enrolledServices") } + + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } // 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, ",")}) @@ -259,6 +289,15 @@ func resourceAccessApprovalFolderSettingsRead(d *schema.ResourceData, meta inter if err := d.Set("enrolled_ancestor", flattenAccessApprovalFolderSettingsEnrolledAncestor(res["enrolledAncestor"], d, config)); err != nil { return fmt.Errorf("Error reading FolderSettings: %s", err) } + if err := d.Set("active_key_version", flattenAccessApprovalFolderSettingsActiveKeyVersion(res["activeKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading FolderSettings: %s", err) + } + if err := d.Set("ancestor_has_active_key_version", flattenAccessApprovalFolderSettingsAncestorHasActiveKeyVersion(res["ancestorHasActiveKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading FolderSettings: %s", err) + } + if err := d.Set("invalid_key_version", flattenAccessApprovalFolderSettingsInvalidKeyVersion(res["invalidKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading FolderSettings: %s", err) + } return nil } @@ -285,6 +324,12 @@ func resourceAccessApprovalFolderSettingsUpdate(d *schema.ResourceData, meta int } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalFolderSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}folders/{{folder_id}}/accessApprovalSettings") if err != nil { @@ -301,6 +346,10 @@ func resourceAccessApprovalFolderSettingsUpdate(d *schema.ResourceData, meta int if d.HasChange("enrolled_services") { updateMask = append(updateMask, "enrolledServices") } + + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } // 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, ",")}) @@ -334,6 +383,7 @@ func resourceAccessApprovalFolderSettingsDelete(d *schema.ResourceData, meta int obj := make(map[string]interface{}) obj["notificationEmails"] = []string{} obj["enrolledServices"] = []string{} + obj["activeKeyVersion"] = "" url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}folders/{{folder_id}}/accessApprovalSettings") if err != nil { @@ -345,6 +395,7 @@ func resourceAccessApprovalFolderSettingsDelete(d *schema.ResourceData, meta int updateMask = append(updateMask, "notificationEmails") updateMask = append(updateMask, "enrolledServices") + updateMask = append(updateMask, "activeKeyVersion") // updateMask is a URL parameter but not present in the schema, so replaceVars // won't set it @@ -425,6 +476,18 @@ func flattenAccessApprovalFolderSettingsEnrolledAncestor(v interface{}, d *schem return v } +func flattenAccessApprovalFolderSettingsActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalFolderSettingsAncestorHasActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalFolderSettingsInvalidKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func expandAccessApprovalFolderSettingsNotificationEmails(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { v = v.(*schema.Set).List() return v, nil @@ -467,3 +530,7 @@ func expandAccessApprovalFolderSettingsEnrolledServicesCloudProduct(v interface{ func expandAccessApprovalFolderSettingsEnrolledServicesEnrollmentLevel(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } + +func expandAccessApprovalFolderSettingsActiveKeyVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_organization_access_approval_settings.go b/google/resource_organization_access_approval_settings.go index 05aed7d47a7..67d2ae773b9 100644 --- a/google/resource_organization_access_approval_settings.go +++ b/google/resource_organization_access_approval_settings.go @@ -59,6 +59,12 @@ A maximum of 10 enrolled services will be enforced, to be expanded as the set of ForceNew: true, Description: `ID of the organization of the access approval settings.`, }, + "active_key_version": { + Type: schema.TypeString, + Optional: true, + Description: `The asymmetric crypto key version to use for signing approval requests. +Empty active_key_version indicates that a Google-managed key should be used for signing.`, + }, "notification_emails": { Type: schema.TypeSet, Computed: true, @@ -72,11 +78,23 @@ resources of that resource. A maximum of 50 email addresses are allowed.`, }, Set: schema.HashString, }, + "ancestor_has_active_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `This field will always be unset for the organization since organizations do not have ancestors.`, + }, "enrolled_ancestor": { Type: schema.TypeBool, Computed: true, Description: `This field will always be unset for the organization since organizations do not have ancestors.`, }, + "invalid_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `If the field is true, that indicates that there is some configuration issue with the active_key_version +configured on this Organization (e.g. it doesn't exist or the Access Approval service account doesn't have the +correct permissions on it, etc.).`, + }, "name": { Type: schema.TypeString, Computed: true, @@ -136,6 +154,12 @@ func resourceAccessApprovalOrganizationSettingsCreate(d *schema.ResourceData, me } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(enrolledServicesProp)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalOrganizationSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(activeKeyVersionProp)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}organizations/{{organization_id}}/accessApprovalSettings") if err != nil { @@ -159,6 +183,10 @@ func resourceAccessApprovalOrganizationSettingsCreate(d *schema.ResourceData, me if d.HasChange("enrolled_services") { updateMask = append(updateMask, "enrolledServices") } + + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } // 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, ",")}) @@ -221,6 +249,15 @@ func resourceAccessApprovalOrganizationSettingsRead(d *schema.ResourceData, meta if err := d.Set("enrolled_ancestor", flattenAccessApprovalOrganizationSettingsEnrolledAncestor(res["enrolledAncestor"], d, config)); err != nil { return fmt.Errorf("Error reading OrganizationSettings: %s", err) } + if err := d.Set("active_key_version", flattenAccessApprovalOrganizationSettingsActiveKeyVersion(res["activeKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationSettings: %s", err) + } + if err := d.Set("ancestor_has_active_key_version", flattenAccessApprovalOrganizationSettingsAncestorHasActiveKeyVersion(res["ancestorHasActiveKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationSettings: %s", err) + } + if err := d.Set("invalid_key_version", flattenAccessApprovalOrganizationSettingsInvalidKeyVersion(res["invalidKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationSettings: %s", err) + } return nil } @@ -247,6 +284,12 @@ func resourceAccessApprovalOrganizationSettingsUpdate(d *schema.ResourceData, me } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalOrganizationSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}organizations/{{organization_id}}/accessApprovalSettings") if err != nil { @@ -263,6 +306,10 @@ func resourceAccessApprovalOrganizationSettingsUpdate(d *schema.ResourceData, me if d.HasChange("enrolled_services") { updateMask = append(updateMask, "enrolledServices") } + + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } // 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, ",")}) @@ -296,6 +343,7 @@ func resourceAccessApprovalOrganizationSettingsDelete(d *schema.ResourceData, me obj := make(map[string]interface{}) obj["notificationEmails"] = []string{} obj["enrolledServices"] = []string{} + obj["activeKeyVersion"] = "" url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}organizations/{{organization_id}}/accessApprovalSettings") if err != nil { @@ -307,6 +355,7 @@ func resourceAccessApprovalOrganizationSettingsDelete(d *schema.ResourceData, me updateMask = append(updateMask, "notificationEmails") updateMask = append(updateMask, "enrolledServices") + updateMask = append(updateMask, "activeKeyVersion") // updateMask is a URL parameter but not present in the schema, so replaceVars // won't set it @@ -387,6 +436,18 @@ func flattenAccessApprovalOrganizationSettingsEnrolledAncestor(v interface{}, d return v } +func flattenAccessApprovalOrganizationSettingsActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalOrganizationSettingsAncestorHasActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalOrganizationSettingsInvalidKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func expandAccessApprovalOrganizationSettingsNotificationEmails(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { v = v.(*schema.Set).List() return v, nil @@ -429,3 +490,7 @@ func expandAccessApprovalOrganizationSettingsEnrolledServicesCloudProduct(v inte func expandAccessApprovalOrganizationSettingsEnrolledServicesEnrollmentLevel(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } + +func expandAccessApprovalOrganizationSettingsActiveKeyVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_project_access_approval_settings.go b/google/resource_project_access_approval_settings.go index f0c74ddc6c0..127a8f2b672 100644 --- a/google/resource_project_access_approval_settings.go +++ b/google/resource_project_access_approval_settings.go @@ -59,6 +59,13 @@ A maximum of 10 enrolled services will be enforced, to be expanded as the set of ForceNew: true, Description: `ID of the project of the access approval settings.`, }, + "active_key_version": { + Type: schema.TypeString, + Optional: true, + Description: `The asymmetric crypto key version to use for signing approval requests. +Empty active_key_version indicates that a Google-managed key should be used for signing. +This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set.`, + }, "notification_emails": { Type: schema.TypeSet, Computed: true, @@ -78,11 +85,24 @@ resources of that resource. A maximum of 50 email addresses are allowed.`, Deprecated: "Deprecated in favor of `project_id`", Description: `Deprecated in favor of 'project_id'`, }, + "ancestor_has_active_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `If the field is true, that indicates that an ancestor of this Project has set active_key_version.`, + }, "enrolled_ancestor": { Type: schema.TypeBool, Computed: true, Description: `If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Project.`, }, + "invalid_key_version": { + Type: schema.TypeBool, + Computed: true, + Description: `If the field is true, that indicates that there is some configuration issue with the active_key_version +configured on this Project (e.g. it doesn't exist or the Access Approval service account doesn't have the +correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, +as key versions are inherited top-down.`, + }, "name": { Type: schema.TypeString, Computed: true, @@ -142,6 +162,12 @@ func resourceAccessApprovalProjectSettingsCreate(d *schema.ResourceData, meta in } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(enrolledServicesProp)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalProjectSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(activeKeyVersionProp)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } projectProp, err := expandAccessApprovalProjectSettingsProject(d.Get("project"), d, config) if err != nil { return err @@ -172,6 +198,10 @@ func resourceAccessApprovalProjectSettingsCreate(d *schema.ResourceData, meta in updateMask = append(updateMask, "enrolledServices") } + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } + if d.HasChange("project") { updateMask = append(updateMask, "project") } @@ -237,6 +267,15 @@ func resourceAccessApprovalProjectSettingsRead(d *schema.ResourceData, meta inte if err := d.Set("enrolled_ancestor", flattenAccessApprovalProjectSettingsEnrolledAncestor(res["enrolledAncestor"], d, config)); err != nil { return fmt.Errorf("Error reading ProjectSettings: %s", err) } + if err := d.Set("active_key_version", flattenAccessApprovalProjectSettingsActiveKeyVersion(res["activeKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading ProjectSettings: %s", err) + } + if err := d.Set("ancestor_has_active_key_version", flattenAccessApprovalProjectSettingsAncestorHasActiveKeyVersion(res["ancestorHasActiveKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading ProjectSettings: %s", err) + } + if err := d.Set("invalid_key_version", flattenAccessApprovalProjectSettingsInvalidKeyVersion(res["invalidKeyVersion"], d, config)); err != nil { + return fmt.Errorf("Error reading ProjectSettings: %s", err) + } if err := d.Set("project", flattenAccessApprovalProjectSettingsProject(res["project"], d, config)); err != nil { return fmt.Errorf("Error reading ProjectSettings: %s", err) } @@ -266,6 +305,12 @@ func resourceAccessApprovalProjectSettingsUpdate(d *schema.ResourceData, meta in } else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) { obj["enrolledServices"] = enrolledServicesProp } + activeKeyVersionProp, err := expandAccessApprovalProjectSettingsActiveKeyVersion(d.Get("active_key_version"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("active_key_version"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, activeKeyVersionProp)) { + obj["activeKeyVersion"] = activeKeyVersionProp + } projectProp, err := expandAccessApprovalProjectSettingsProject(d.Get("project"), d, config) if err != nil { return err @@ -289,6 +334,10 @@ func resourceAccessApprovalProjectSettingsUpdate(d *schema.ResourceData, meta in updateMask = append(updateMask, "enrolledServices") } + if d.HasChange("active_key_version") { + updateMask = append(updateMask, "activeKeyVersion") + } + if d.HasChange("project") { updateMask = append(updateMask, "project") } @@ -325,6 +374,7 @@ func resourceAccessApprovalProjectSettingsDelete(d *schema.ResourceData, meta in obj := make(map[string]interface{}) obj["notificationEmails"] = []string{} obj["enrolledServices"] = []string{} + obj["activeKeyVersion"] = "" url, err := replaceVars(d, config, "{{AccessApprovalBasePath}}projects/{{project_id}}/accessApprovalSettings") if err != nil { @@ -336,6 +386,7 @@ func resourceAccessApprovalProjectSettingsDelete(d *schema.ResourceData, meta in updateMask = append(updateMask, "notificationEmails") updateMask = append(updateMask, "enrolledServices") + updateMask = append(updateMask, "activeKeyVersion") // updateMask is a URL parameter but not present in the schema, so replaceVars // won't set it @@ -416,6 +467,18 @@ func flattenAccessApprovalProjectSettingsEnrolledAncestor(v interface{}, d *sche return v } +func flattenAccessApprovalProjectSettingsActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalProjectSettingsAncestorHasActiveKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenAccessApprovalProjectSettingsInvalidKeyVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenAccessApprovalProjectSettingsProject(v interface{}, d *schema.ResourceData, config *Config) interface{} { return v } @@ -463,6 +526,10 @@ func expandAccessApprovalProjectSettingsEnrolledServicesEnrollmentLevel(v interf return v, nil } +func expandAccessApprovalProjectSettingsActiveKeyVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandAccessApprovalProjectSettingsProject(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } diff --git a/website/docs/d/access_approval_folder_service_account.html.markdown b/website/docs/d/access_approval_folder_service_account.html.markdown new file mode 100644 index 00000000000..1394ca46a63 --- /dev/null +++ b/website/docs/d/access_approval_folder_service_account.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Access Approval" +layout: "google" +page_title: "Google: google_access_approval_folder_service_account" +sidebar_current: "docs-google-datasource-access-approval-folder-service-account" +description: |- + Get the email address of a folder's Access Approval service account. +--- + +# google\_access\_approval\_folder\_service\_account + +Get the email address of a folder's Access Approval service account. + +Each Google Cloud folder has a unique service account used by Access Approval. +When using Access Approval with a +[custom signing key](https://cloud.google.com/cloud-provider-access-management/access-approval/docs/review-approve-access-requests-custom-keys), +this account needs to be granted the `cloudkms.signerVerifier` IAM role on the +Cloud KMS key used to sign approvals. + +## Example Usage + +```hcl +data "google_access_approval_folder_service_account" "service_account" { + folder_id = "my-folder" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_folder_service_account.service_account.account_email}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `folder_id` - (Required) The folder ID the service account was created for. + +## Attributes Reference + +The following attributes are exported: + +* `name` - The Access Approval service account resource name. Format is "folders/{folder_id}/serviceAccount". + +* `account_email` - The email address of the service account. This value is +often used to refer to the service account in order to grant IAM permissions. diff --git a/website/docs/d/access_approval_organization_service_account.html.markdown b/website/docs/d/access_approval_organization_service_account.html.markdown new file mode 100644 index 00000000000..590c0b7d01e --- /dev/null +++ b/website/docs/d/access_approval_organization_service_account.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Access Approval" +layout: "google" +page_title: "Google: google_access_approval_organization_service_account" +sidebar_current: "docs-google-datasource-access-approval-organization-service-account" +description: |- + Get the email address of an organization's Access Approval service account. +--- + +# google\_access\_approval\_organization\_service\_account + +Get the email address of an organization's Access Approval service account. + +Each Google Cloud organization has a unique service account used by Access Approval. +When using Access Approval with a +[custom signing key](https://cloud.google.com/cloud-provider-access-management/access-approval/docs/review-approve-access-requests-custom-keys), +this account needs to be granted the `cloudkms.signerVerifier` IAM role on the +Cloud KMS key used to sign approvals. + +## Example Usage + +```hcl +data "google_access_approval_organization_service_account" "service_account" { + organization_id = "my-organization" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_organization_service_account.service_account.account_email}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `organization_id` - (Required) The organization ID the service account was created for. + +## Attributes Reference + +The following attributes are exported: + +* `name` - The Access Approval service account resource name. Format is "organizations/{organization_id}/serviceAccount". + +* `account_email` - The email address of the service account. This value is +often used to refer to the service account in order to grant IAM permissions. diff --git a/website/docs/d/access_approval_project_service_account.html.markdown b/website/docs/d/access_approval_project_service_account.html.markdown new file mode 100644 index 00000000000..e36798a2c33 --- /dev/null +++ b/website/docs/d/access_approval_project_service_account.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Access Approval" +layout: "google" +page_title: "Google: google_access_approval_project_service_account" +sidebar_current: "docs-google-datasource-access-approval-project-service-account" +description: |- + Get the email address of a project's Access Approval service account. +--- + +# google\_access\_approval\_project\_service\_account + +Get the email address of a project's Access Approval service account. + +Each Google Cloud project has a unique service account used by Access Approval. +When using Access Approval with a +[custom signing key](https://cloud.google.com/cloud-provider-access-management/access-approval/docs/review-approve-access-requests-custom-keys), +this account needs to be granted the `cloudkms.signerVerifier` IAM role on the +Cloud KMS key used to sign approvals. + +## Example Usage + +```hcl +data "google_access_approval_project_service_account" "service_account" { + project_id = "my-project" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_project_service_account.service_account.account_email}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `project_id` - (Required) The project ID the service account was created for. + +## Attributes Reference + +The following attributes are exported: + +* `name` - The Access Approval service account resource name. Format is "projects/{project_id}/serviceAccount". + +* `account_email` - The email address of the service account. This value is +often used to refer to the service account in order to grant IAM permissions. diff --git a/website/docs/r/folder_access_approval_settings.html.markdown b/website/docs/r/folder_access_approval_settings.html.markdown index 21af6372402..cf98ba0a279 100644 --- a/website/docs/r/folder_access_approval_settings.html.markdown +++ b/website/docs/r/folder_access_approval_settings.html.markdown @@ -47,6 +47,62 @@ resource "google_folder_access_approval_settings" "folder_access_approval" { } } ``` +## Example Usage - Folder Access Approval Active Key Version + + +```hcl +resource "google_folder" "my_folder" { + display_name = "my-folder" + parent = "organizations/123456789" +} + +resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + folder_id = google_folder.my_folder.name +} + +resource "google_kms_key_ring" "key_ring" { + name = "key-ring" + location = "global" + project = google_project.my_project.project_id +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "crypto-key" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_folder_service_account" "service_account" { + folder_id = google_folder.my_folder.folder_id +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_folder_service_account.service_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_folder_access_approval_settings" "folder_access_approval" { + folder_id = google_folder.my_folder.folder_id + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + enrolled_services { + cloud_product = "all" + } + + depends_on = [google_kms_crypto_key_iam_member.iam] +} +``` ## Argument Reference @@ -109,6 +165,12 @@ The following arguments are supported: Notifications relating to a resource will be sent to all emails in the settings of ancestor resources of that resource. A maximum of 50 email addresses are allowed. +* `active_key_version` - + (Optional) + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set. + ## Attributes Reference @@ -122,6 +184,15 @@ In addition to the arguments listed above, the following computed attributes are * `enrolled_ancestor` - If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Folder. +* `ancestor_has_active_key_version` - + If the field is true, that indicates that an ancestor of this Folder has set active_key_version. + +* `invalid_key_version` - + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Folder (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, + as key versions are inherited top-down. + ## Timeouts diff --git a/website/docs/r/organization_access_approval_settings.html.markdown b/website/docs/r/organization_access_approval_settings.html.markdown index 6382d9cb2bd..b06b085387c 100644 --- a/website/docs/r/organization_access_approval_settings.html.markdown +++ b/website/docs/r/organization_access_approval_settings.html.markdown @@ -47,6 +47,57 @@ resource "google_organization_access_approval_settings" "organization_access_app } } ``` +## Example Usage - Organization Access Approval Active Key Version + + +```hcl +resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + org_id = "123456789" +} + +resource "google_kms_key_ring" "key_ring" { + name = "key-ring" + location = "global" + project = google_project.my_project.project_id +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "crypto-key" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_organization_service_account" "service_account" { + organization_id = "123456789" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_organization_service_account.service_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_organization_access_approval_settings" "organization_access_approval" { + organization_id = "123456789" + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + enrolled_services { + cloud_product = "all" + } + + depends_on = [google_kms_crypto_key_iam_member.iam] +} +``` ## Argument Reference @@ -97,6 +148,11 @@ The following arguments are supported: Notifications relating to a resource will be sent to all emails in the settings of ancestor resources of that resource. A maximum of 50 email addresses are allowed. +* `active_key_version` - + (Optional) + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + ## Attributes Reference @@ -110,6 +166,14 @@ In addition to the arguments listed above, the following computed attributes are * `enrolled_ancestor` - This field will always be unset for the organization since organizations do not have ancestors. +* `ancestor_has_active_key_version` - + This field will always be unset for the organization since organizations do not have ancestors. + +* `invalid_key_version` - + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Organization (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.). + ## Timeouts diff --git a/website/docs/r/project_access_approval_settings.html.markdown b/website/docs/r/project_access_approval_settings.html.markdown index b7125a3b814..04dde5fefa4 100644 --- a/website/docs/r/project_access_approval_settings.html.markdown +++ b/website/docs/r/project_access_approval_settings.html.markdown @@ -43,6 +43,51 @@ resource "google_project_access_approval_settings" "project_access_approval" { } } ``` +## Example Usage - Project Access Approval Active Key Version + + +```hcl +resource "google_kms_key_ring" "key_ring" { + name = "key-ring" + location = "global" + project = "my-project-name" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "crypto-key" + key_ring = google_kms_key_ring.key_ring.id + purpose = "ASYMMETRIC_SIGN" + + version_template { + algorithm = "EC_SIGN_P384_SHA384" + } +} + +data "google_access_approval_project_service_account" "service_account" { + project_id = "my-project-name" +} + +resource "google_kms_crypto_key_iam_member" "iam" { + crypto_key_id = google_kms_crypto_key.crypto_key.id + role = "roles/cloudkms.signerVerifier" + member = "serviceAccount:${data.google_access_approval_project_service_account.service_account.account_email}" +} + +data "google_kms_crypto_key_version" "crypto_key_version" { + crypto_key = google_kms_crypto_key.crypto_key.id +} + +resource "google_project_access_approval_settings" "project_access_approval" { + project_id = "my-project-name" + active_key_version = data.google_kms_crypto_key_version.crypto_key_version.name + + enrolled_services { + cloud_product = "all" + } + + depends_on = [google_kms_crypto_key_iam_member.iam] +} +``` ## Argument Reference @@ -93,6 +138,12 @@ The following arguments are supported: Notifications relating to a resource will be sent to all emails in the settings of ancestor resources of that resource. A maximum of 50 email addresses are allowed. +* `active_key_version` - + (Optional) + The asymmetric crypto key version to use for signing approval requests. + Empty active_key_version indicates that a Google-managed key should be used for signing. + This property will be ignored if set by an ancestor of the resource, and new non-empty values may not be set. + * `project` - (Optional, Deprecated) Deprecated in favor of `project_id` @@ -110,6 +161,15 @@ In addition to the arguments listed above, the following computed attributes are * `enrolled_ancestor` - If the field is true, that indicates that at least one service is enrolled for Access Approval in one or more ancestors of the Project. +* `ancestor_has_active_key_version` - + If the field is true, that indicates that an ancestor of this Project has set active_key_version. + +* `invalid_key_version` - + If the field is true, that indicates that there is some configuration issue with the active_key_version + configured on this Project (e.g. it doesn't exist or the Access Approval service account doesn't have the + correct permissions on it, etc.) This key version is not necessarily the effective key version at this level, + as key versions are inherited top-down. + ## Timeouts diff --git a/website/google.erb b/website/google.erb index 9ecf63e38bd..233ce230cc5 100644 --- a/website/google.erb +++ b/website/google.erb @@ -85,6 +85,24 @@
  • Access Approval