Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add IAM support for storage bucket #822

Merged
merged 8 commits into from
Dec 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions google/iam_storage_bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package google

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/storage/v1"
)

var IamStorageBucketSchema = map[string]*schema.Schema{
"bucket": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
}

type StorageBucketIamUpdater struct {
bucket string
Config *Config
}

func NewStorageBucketIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
bucket := d.Get("bucket").(string)

return &StorageBucketIamUpdater{
bucket: bucket,
Config: config,
}, nil
}

func (u *StorageBucketIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
p, err := u.Config.clientStorage.Buckets.GetIamPolicy(u.bucket).Do()
if err != nil {
return nil, fmt.Errorf("Error retrieving IAM policy for %s: %s", u.DescribeResource(), err)
}

cloudResourcePolicy, err := storageToResourceManagerPolicy(p)
if err != nil {
return nil, fmt.Errorf("Invalid IAM policy for %s: %s", u.DescribeResource(), err)
}

return cloudResourcePolicy, nil
}

func (u *StorageBucketIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
storagePolicy, err := resourceManagerToStoragePolicy(policy)

if err != nil {
return fmt.Errorf("Invalid IAM policy for %s: %s", u.DescribeResource(), err)
}

_, err = u.Config.clientStorage.Buckets.SetIamPolicy(u.bucket, storagePolicy).Do()

if err != nil {
return fmt.Errorf("Error setting IAM policy for %s: %s", u.DescribeResource(), err)
}

return nil
}

func (u *StorageBucketIamUpdater) GetResourceId() string {
return u.bucket
}

func (u *StorageBucketIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-storage-bucket-%s", u.bucket)
}

func (u *StorageBucketIamUpdater) DescribeResource() string {
return fmt.Sprintf("Storage Bucket %q", u.bucket)
}

func resourceManagerToStoragePolicy(p *cloudresourcemanager.Policy) (policy *storage.Policy, err error) {
policy = &storage.Policy{}
err = Convert(p, policy)
return
}

func storageToResourceManagerPolicy(p *storage.Policy) (policy *cloudresourcemanager.Policy, err error) {
policy = &cloudresourcemanager.Policy{}
err = Convert(p, policy)
return
}
9 changes: 7 additions & 2 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,13 @@ func Provider() terraform.ResourceProvider {
"google_service_account_key": resourceGoogleServiceAccountKey(),
"google_storage_bucket": resourceStorageBucket(),
"google_storage_bucket_acl": resourceStorageBucketAcl(),
"google_storage_bucket_object": resourceStorageBucketObject(),
"google_storage_object_acl": resourceStorageObjectAcl(),
// Legacy roles such as roles/storage.legacyBucketReader are automatically added
// when creating a bucket. For this reason, it is better not to add the authoritative
// google_storage_bucket_iam_policy resource.
"google_storage_bucket_iam_binding": ResourceIamBinding(IamStorageBucketSchema, NewStorageBucketIamUpdater),
"google_storage_bucket_iam_member": ResourceIamMember(IamStorageBucketSchema, NewStorageBucketIamUpdater),
"google_storage_bucket_object": resourceStorageBucketObject(),
"google_storage_object_acl": resourceStorageObjectAcl(),
},

ConfigureFunc: providerConfigure,
Expand Down
8 changes: 7 additions & 1 deletion google/resource_iam_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@ func resourceIamMemberDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.De
return nil
}
binding.Members = append(binding.Members[:memberToRemove], binding.Members[memberToRemove+1:]...)
p.Bindings[bindingToRemove] = binding
if len(binding.Members) == 0 {
// If there is no member left for the role, remove the binding altogether
p.Bindings = append(p.Bindings[:bindingToRemove], p.Bindings[bindingToRemove+1:]...)
} else {
p.Bindings[bindingToRemove] = binding
}

return nil
})
if err != nil {
Expand Down
153 changes: 153 additions & 0 deletions google/resource_storage_bucket_iam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package google

import (
"fmt"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"reflect"
"sort"
"testing"
)

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

bucket := acctest.RandomWithPrefix("tf-test")
account := acctest.RandomWithPrefix("tf-test")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test IAM Binding creation
Config: testAccGoogleStorageBucketIamBinding_basic(bucket, account),
Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{
fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
{
// Test IAM Binding update
Config: testAccGoogleStorageBucketIamBinding_update(bucket, account),
Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{
fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
},
})
}

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

bucket := acctest.RandomWithPrefix("tf-test")
account := acctest.RandomWithPrefix("tf-test")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test Iam Member creation (no update for member, no need to test)
Config: testAccGoogleStorageBucketIamMember_basic(bucket, account),
Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.admin", []string{
fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
},
})
}

func testAccCheckGoogleStorageBucketIam(bucket, role string, members []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
p, err := config.clientStorage.Buckets.GetIamPolicy(bucket).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)
}
}

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

func testAccGoogleStorageBucketIamBinding_basic(bucket, account string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
}

resource "google_service_account" "test-account-1" {
account_id = "%s-1"
display_name = "Iam Testing Account"
}

resource "google_storage_bucket_iam_binding" "foo" {
bucket = "${google_storage_bucket.bucket.name}"
role = "roles/storage.objectViewer"
members = [
"serviceAccount:${google_service_account.test-account-1.email}",
]
}
`, bucket, account)
}

func testAccGoogleStorageBucketIamBinding_update(bucket, account string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
}

resource "google_service_account" "test-account-1" {
account_id = "%s-1"
display_name = "Iam Testing Account"
}

resource "google_service_account" "test-account-2" {
account_id = "%s-2"
display_name = "Iam Testing Account"
}

resource "google_storage_bucket_iam_binding" "foo" {
bucket = "${google_storage_bucket.bucket.name}"
role = "roles/storage.objectViewer"
members = [
"serviceAccount:${google_service_account.test-account-1.email}",
"serviceAccount:${google_service_account.test-account-2.email}",
]
}
`, bucket, account, account)
}

func testAccGoogleStorageBucketIamMember_basic(bucket, account string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
}

resource "google_service_account" "test-account-1" {
account_id = "%s-1"
display_name = "Iam Testing Account"
}

resource "google_storage_bucket_iam_member" "foo" {
bucket = "${google_storage_bucket.bucket.name}"
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.test-account-1.email}"
}
`, bucket, account)
}
63 changes: 63 additions & 0 deletions website/docs/r/storage_bucket_iam.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
layout: "google"
page_title: "Google: google_storage_bucket_iam"
sidebar_current: "docs-google-storage-bucket-iam"
description: |-
Collection of resources to manage IAM policy for a Google storage bucket.
---

# IAM policy for Google storage bucket

Two different resources help you manage your IAM policy for storage bucket. Each of these resources serves a different use case:

* `google_storage_bucket_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 storage bucket are preserved.
* `google_storage_bucket_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the storage bucket are preserved.

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

## google\_storage\_bucket\_iam\_binding

```hcl
resource "google_storage_bucket_iam_binding" "binding" {
bucket = "your-bucket-name"
role = "roles/storage.objectViewer"

members = [
"user:jane@example.com",
]
}
```

## google\_storage\_bucket\_iam\_member

```hcl
resource "google_storage_bucket_iam_member" "member" {
bucket = "your-bucket-name"
role = "roles/storage.objectViewer"
member = "user:jane@example.com"
}
```

## Argument Reference

The following arguments are supported:

* `bucket` - (Required) The name of the bucket it applies to.

* `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 Google Apps domain name that represents all the users of that domain. For example, google.com or example.com.

* `role` - (Required) The role that should be applied.

## Attributes Reference

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

* `etag` - (Computed) The etag of the storage bucket's IAM policy.
16 changes: 15 additions & 1 deletion website/google.erb
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,14 @@
<a href="/docs/providers/google/r/storage_bucket_acl.html">google_storage_bucket_acl</a>
</li>

<li<%= sidebar_current("docs-google-storage-bucket-iam") %>>
<a href="/docs/providers/google/r/storage_bucket_iam.html">google_storage_bucket_iam_binding</a>
</li>

<li<%= sidebar_current("docs-google-storage-bucket-iam") %>>
<a href="/docs/providers/google/r/storage_bucket_iam.html">google_storage_bucket_iam_member</a>
</li>

<li<%= sidebar_current("docs-google-storage-bucket-object") %>>
<a href="/docs/providers/google/r/storage_bucket_object.html">google_storage_bucket_object</a>
</li>
Expand All @@ -451,7 +459,13 @@
<a href="/docs/providers/google/r/google_kms_key_ring.html">google_kms_key_ring</a>
</li>
<li<%= sidebar_current("docs-google-kms-key-ring-iam") %>>
<a href="/docs/providers/google/r/google_kms_key_ring_iam.html">google_kms_key_ring_iam_*</a>
<a href="/docs/providers/google/r/google_kms_key_ring_iam.html">google_kms_key_ring_iam_binding</a>
</li>
<li<%= sidebar_current("docs-google-kms-key-ring-iam") %>>
<a href="/docs/providers/google/r/google_kms_key_ring_iam.html">google_kms_key_ring_iam_member</a>
</li>
<li<%= sidebar_current("docs-google-kms-key-ring-iam") %>>
<a href="/docs/providers/google/r/google_kms_key_ring_iam.html">google_kms_key_ring_iam_policy</a>
</li>
<li<%= sidebar_current("docs-google-kms-crypto-key-x") %>>
<a href="/docs/providers/google/r/google_kms_crypto_key.html">google_kms_crypto_key</a>
Expand Down