Skip to content

Commit

Permalink
Add authoritative IAM policy binding for KMS Crypto Keys
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
danawillow authored and modular-magician committed Dec 20, 2019
1 parent e45c6a2 commit aaf7e07
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 256 deletions.
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
"google_kms_key_ring_iam_policy": ResourceIamPolicy(IamKmsKeyRingSchema, NewKmsKeyRingIamUpdater, KeyRingIdParseFunc),
"google_kms_crypto_key_iam_binding": ResourceIamBinding(IamKmsCryptoKeySchema, NewKmsCryptoKeyIamUpdater, CryptoIdParseFunc),
"google_kms_crypto_key_iam_member": ResourceIamMember(IamKmsCryptoKeySchema, NewKmsCryptoKeyIamUpdater, CryptoIdParseFunc),
"google_kms_crypto_key_iam_policy": ResourceIamPolicy(IamKmsCryptoKeySchema, NewKmsCryptoKeyIamUpdater, CryptoIdParseFunc),
"google_service_networking_connection": resourceServiceNetworkingConnection(),
"google_spanner_instance_iam_binding": ResourceIamBinding(IamSpannerInstanceSchema, NewSpannerInstanceIamUpdater, SpannerInstanceIdParseFunc),
"google_spanner_instance_iam_member": ResourceIamMember(IamSpannerInstanceSchema, NewSpannerInstanceIamUpdater, SpannerInstanceIdParseFunc),
Expand Down
125 changes: 125 additions & 0 deletions google/resource_kms_crypto_key_iam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ func TestAccKmsCryptoKeyIamMember(t *testing.T) {
})
}

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

orgId := getTestOrgFromEnv(t)
projectId := acctest.RandomWithPrefix("tf-test")
billingAccount := getTestBillingAccountFromEnv(t)
account := acctest.RandomWithPrefix("tf-test")
roleId := "roles/cloudkms.cryptoKeyEncrypter"
keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

keyRingId := &kmsKeyRingId{
Project: projectId,
Location: DEFAULT_KMS_TEST_LOCATION,
Name: keyRingName,
}
cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccKmsCryptoKeyIamPolicy_basic(projectId, orgId, billingAccount, account, keyRingName, cryptoKeyName, roleId),
Check: testAccCheckGoogleCryptoKmsKeyIam(roleId, []string{
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId),
}),
},
{
ResourceName: "google_kms_crypto_key_iam_policy.foo",
ImportStateId: fmt.Sprintf("%s/%s", keyRingId.terraformId(), cryptoKeyName),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckGoogleKmsCryptoKeyIamBindingExists(bindingResourceName, roleId string, members []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bindingRs, ok := s.RootModule().Resources[fmt.Sprintf("google_kms_crypto_key_iam_binding.%s", bindingResourceName)]
Expand Down Expand Up @@ -170,6 +207,44 @@ func testAccCheckGoogleKmsCryptoKeyIamMemberExists(n, role, member string) resou
}
}

func testAccCheckGoogleCryptoKmsKeyIam(role string, members []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["google_kms_crypto_key_iam_policy"]
if !ok {
return fmt.Errorf("IAM policy resource not found")
}

config := testAccProvider.Meta().(*Config)
cryptoKeyId, err := parseKmsCryptoKeyId(rs.Primary.Attributes["crypto_key_id"], config)

if err != nil {
return err
}

p, err := config.clientKms.Projects.Locations.KeyRings.GetIamPolicy(cryptoKeyId.cryptoKeyId()).Do()
if err != nil {
return err
}

for _, binding := range p.Bindings {
if binding.Role == role {
sort.Strings(members)
sort.Strings(binding.Members)

if reflect.DeepEqual(members, binding.Members) {
return nil
}

return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members)
} else {
return fmt.Errorf("Binding found but not expected for role: %v", binding.Role)
}
}

return fmt.Errorf("No binding for role %q", role)
}
}

// We are using a custom role since iam_binding is authoritative on the member list and
// we want to avoid removing members from an existing role to prevent unwanted side effects.
func testAccKmsCryptoKeyIamBinding_basic(projectId, orgId, billingAccount, account, keyRingName, cryptoKeyName, roleId string) string {
Expand Down Expand Up @@ -312,3 +387,53 @@ resource "google_kms_crypto_key_iam_member" "foo" {
}
`, projectId, orgId, billingAccount, account, keyRingName, cryptoKeyName, roleId)
}

func testAccKmsCryptoKeyIamPolicy_basic(projectId, orgId, billingAccount, account, keyRingName, cryptoKeyName, roleId string) string {
return fmt.Sprintf(`
resource "google_project" "test_project" {
name = "Test project"
project_id = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project_service" "kms" {
project = google_project.test_project.project_id
service = "cloudkms.googleapis.com"
}
resource "google_project_service" "iam" {
project = google_project_service.kms.project
service = "iam.googleapis.com"
}
resource "google_service_account" "test_account" {
project = google_project_services.test_project.project
account_id = "%s"
display_name = "Kms Key Ring Iam Testing Account"
}
resource "google_kms_key_ring" "key_ring" {
project = google_project_services.test_project.project
location = "%s"
name = "%s"
}
resource "google_kms_crypto_key" "crypto_key" {
key_ring = google_kms_key_ring.key_ring.id
name = "%s"
}
data "google_iam_policy" "foo" {
binding {
role = "%s"
members = ["serviceAccount:${google_service_account.test_account.email}"]
}
}
resource "google_kms_crypto_key_iam_policy" "foo" {
crypto_key_id = google_kms_crypto_key.crypto_key.id
policy_data = data.google_iam_policy.foo.policy_data
}
`, projectId, orgId, billingAccount, account, DEFAULT_KMS_TEST_LOCATION, keyRingName, cryptoKeyName, roleId)
}
198 changes: 198 additions & 0 deletions website/docs/r/google_kms_crypto_key_iam.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
subcategory: "Cloud KMS"
layout: "google"
page_title: "Google: google_kms_crypto_key_iam"
sidebar_current: "docs-google-kms-crypto-key-iam"
description: |-
Collection of resources to manage IAM policy for a Google Cloud KMS crypto key.
---

# IAM policy for Google Cloud KMS crypto key

Three different resources help you manage your IAM policy for KMS crypto key. Each of these resources serves a different use case:

* `google_kms_crypto_key_iam_policy`: Authoritative. Sets the IAM policy for the crypto key and replaces any existing policy already attached.
* `google_kms_crypto_key_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 crypto key are preserved.
* `google_kms_crypto_key_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the crypto key are preserved.

~> **Note:** `google_kms_crypto_key_iam_policy` **cannot** be used in conjunction with `google_kms_crypto_key_iam_binding` and `google_kms_crypto_key_iam_member` or they will fight over what your policy should be.

~> **Note:** `google_kms_crypto_key_iam_binding` resources **can be** used in conjunction with `google_kms_crypto_key_iam_member` resources **only if** they do not grant privilege to the same role.

# google\_kms\_crypto\_key\_iam\_policy

```hcl
resource "google_kms_key_ring" "keyring" {
name = "keyring-example"
location = "global"
}
resource "google_kms_crypto_key" "key" {
name = "crypto-key-example"
key_ring = google_kms_key_ring.keyring.id
rotation_period = "100000s"
lifecycle {
prevent_destroy = true
}
}
data "google_iam_policy" "admin" {
binding {
role = "roles/cloudkms.cryptoKeyEncrypter"
members = [
"user:[email protected]",
]
}
}
resource "google_kms_crypto_key_iam_policy" "crypto_key" {
crypto_key_id = google_kms_crypto_key.key.id
policy_data = data.google_iam_policy.admin.policy_data
}
```

With IAM Conditions ([beta](https://terraform.io/docs/providers/google/provider_versions.html)):
```hcl
data "google_iam_policy" "admin" {
binding {
role = "roles/cloudkms.cryptoKeyEncrypter"
members = [
"user:[email protected]",
]
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
}
```

# google\_kms\_crypto\_key\_iam\_binding

```hcl
resource "google_kms_crypto_key_iam_binding" "crypto_key" {
crypto_key_id = google_kms_crypto_key.key.id
role = "roles/cloudkms.cryptoKeyEncrypter"
members = [
"user:[email protected]",
]
}
```

With IAM Conditions ([beta](https://terraform.io/docs/providers/google/provider_versions.html)):
```hcl
resource "google_kms_crypto_key_iam_binding" "crypto_key" {
crypto_key_id = google_kms_crypto_key.key.id
role = "roles/cloudkms.cryptoKeyEncrypter"
members = [
"user:[email protected]",
]
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
```

# google\_kms\_crypto\_key\_iam\_member

```hcl
resource "google_kms_crypto_key_iam_member" "crypto_key" {
crypto_key_id = google_kms_crypto_key.key.id
role = "roles/cloudkms.cryptoKeyEncrypter"
member = "user:[email protected]"
}
```

With IAM Conditions ([beta](https://terraform.io/docs/providers/google/provider_versions.html)):
```hcl
resource "google_kms_crypto_key_iam_member" "crypto_key" {
crypto_key_id = google_kms_crypto_key.key.id
role = "roles/cloudkms.cryptoKeyEncrypter"
member = "user:[email protected]"
condition {
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
}
}
```

## Argument Reference

The following arguments are supported:

* `crypto_key_id` - (Required) The crypto key ID, in the form
`{project_id}/{location_name}/{key_ring_name}/{crypto_key_name}` or
`{location_name}/{key_ring_name}/{crypto_key_name}`. In the second form,
the provider's project setting will be used as a fallback.

* `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, [email protected] or [email protected].
* **serviceAccount:{emailid}**: An email address that represents a service account. For example, [email protected].
* **group:{emailid}**: An email address that represents a Google group. For example, [email protected].
* **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. Note that custom roles must be of the format
`[projects|organizations]/{parent-name}/roles/{role-name}`.

* `policy_data` - (Required only by `google_kms_crypto_key_iam_policy`) The policy data generated by
a `google_iam_policy` data source.

* `condition` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) An [IAM Condition](https://cloud.google.com/iam/docs/conditions-overview) for a given binding.
Structure is documented below.

---

The `condition` block supports:

* `expression` - (Required) Textual representation of an expression in Common Expression Language syntax.

* `title` - (Required) A title for the expression, i.e. a short string describing its purpose.

* `description` - (Optional) An optional description of the expression. This is a longer text which describes the expression, e.g. when hovered over it in a UI.

~> **Warning:** Terraform considers the `role` and condition contents (`title`+`description`+`expression`) as the
identifier for the binding. This means that if any part of the condition is changed out-of-band, Terraform will
consider it to be an entirely different resource and will treat it as such.

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
exported:

* `etag` - (Computed) The etag of the project's IAM policy.

## Import

IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `crypto_key_id`, role, and member identity e.g.

```
$ terraform import google_kms_crypto_key_iam_member.crypto_key "your-project-id/location-name/key-ring-name/key-name roles/viewer user:[email protected]"
```

IAM binding imports use space-delimited identifiers; first the resource in question and then the role. These bindings can be imported using the `crypto_key_id` and role, e.g.

```
$ terraform import google_kms_crypto_key_iam_binding.crypto_key "your-project-id/location-name/key-ring-name/key-name roles/editor"
```

IAM policy imports use the identifier of the resource in question. This policy resource can be imported using the `crypto_key_id`, e.g.

```
$ terraform import google_kms_crypto_key_iam_policy.crypto_key your-project-id/location-name/key-ring-name/key-name
```

-> If you're importing a resource with beta features, make sure to include `-provider=google-beta`
as an argument so that Terraform uses the correct provider to import your resource.
Loading

0 comments on commit aaf7e07

Please sign in to comment.