Skip to content

Commit

Permalink
Add hcp_packer_bucket_iam_binding and `hcp_packer_bucket_iam_policy…
Browse files Browse the repository at this point in the history
…` resources (#866)

* Add resources for managing Packer Bucket RBAC

* Add guide for bucket rbac

* Add examples and guide

* Update docs

* Moss's feedback
  • Loading branch information
JenGoldstrich committed Jun 28, 2024
1 parent 1ce8b41 commit 6a44b00
Show file tree
Hide file tree
Showing 13 changed files with 507 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .changelog/852.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
```release-note:feature
New resource: Add `hcp_packer_bucket` resource for managing HCP Packer buckets
New Resource: Add `hcp_packer_bucket_iam_policy` resource for assigning a list of policy bindings to multiple principals for a HCP Packer Bucket
New resource: Add `hcp_packer_bucket_iam_binding` resource for assigning a single role to a principal for a HCP Packer Bucket
```
55 changes: 55 additions & 0 deletions docs/guides/packer-bucket-rbac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
subcategory: ""
page_title: "Managing HCP Packer Bucket IAM Policies"
description: |-
A guide to using HCP Packer bucket resource along with binding or policy resource to manage bucket level access.
---

# Managing HCP Packer Bucket IAM Policies

You can grant specific users, service principals, or groups contributor or admin level access to a specific HCP Packer bucket using either a `hcp_packer_bucket_iam_binding` or `hcp_packer_bucket_iam_policy` resource. Whenever a user is invited to a project they will have read level access to all resources, but you can restrict which of the principals in your project can maintain specific buckets.

A resource's policy is a list of bindings to assign roles to multiple users, groups, or service principals. The `hcp_packer_bucket_iam_policy` resource sets the Bucket IAM policy and replaces any existing policy.

The following example assigns the role `contributor` to a user principal and a service principal for the `production` bucket.

```terraform
data "hcp_iam_policy" "mypolicy" {
bindings = [
{
role = "roles/contributor"
principals = [
"user-principal-id-1",
"service-principal-id-1",
]
},
]
}
resource "hcp_packer_bucket" "production" {
name = "production"
}
resource "hcp_packer_bucket_iam_policy" "example" {
resource_name = hcp_packer_bucket.production.resource_name
policy_data = data.hcp_iam_policy.mypolicy.policy_data
}
```

The following example assigns role contriubtor for a service principal to the production bucket, and also preserves existing bindings.

```terraform
resource "hcp_service_principal" "my-sp" {
name = "my-sp"
}
resource "hcp_packer_bucket" "production" {
name = "production"
}
resource "hcp_packer_bucket_iam_binding" "example" {
resource_name = hcp_packer_bucket.production.resource_name
principal_id = hcp_service_principal.my-sp.resource_id
role = "roles/contributor"
}
```
38 changes: 38 additions & 0 deletions docs/resources/packer_bucket_iam_binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "hcp_packer_bucket_iam_binding Resource - terraform-provider-hcp"
subcategory: ""
description: |-
Updates the HCP Packer Bucket IAM policy to bind a role to a new member. Existing bindings are preserved.
---

# hcp_packer_bucket_iam_binding (Resource)

Updates the HCP Packer Bucket IAM policy to bind a role to a new member. Existing bindings are preserved.

## Example Usage

```terraform
resource "hcp_service_principal" "my-sp" {
name = "my-sp"
}
resource "hcp_packer_bucket" "production" {
name = "production"
}
resource "hcp_packer_bucket_iam_binding" "example" {
resource_name = hcp_packer_bucket.production.resource_name
principal_id = hcp_service_principal.my-sp.resource_id
role = "roles/contributor"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `principal_id` (String) The principal to bind to the given role.
- `resource_name` (String) The bucket's resource name in the format packer/project/<project ID>/bucket/<bucket name>.
- `role` (String) The role name to bind to the given principal.
58 changes: 58 additions & 0 deletions docs/resources/packer_bucket_iam_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "hcp_packer_bucket_iam_policy Resource - terraform-provider-hcp"
subcategory: ""
description: |-
Sets the HCP Packer Bucket IAM policy and replaces any existing policy.
---

# hcp_packer_bucket_iam_policy (Resource)

Sets the HCP Packer Bucket IAM policy and replaces any existing policy.

## Example Usage

```terraform
data "hcp_iam_policy" "mypolicy" {
bindings = [
{
role = "roles/contributor"
principals = [
"user-principal-id-1",
"service-principal-id-1",
]
},
]
}
resource "hcp_packer_bucket" "production" {
name = "production"
}
resource "hcp_packer_bucket_iam_policy" "example" {
resource_name = hcp_packer_bucket.production.resource_name
policy_data = data.hcp_iam_policy.mypolicy.policy_data
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `policy_data` (String) The policy to apply.
- `resource_name` (String) The bucket's resource name in the format packer/project/<project ID>/bucket/<bucket name>.

### Read-Only

- `etag` (String) The etag captures the existing state of the policy.

## Import

Import is supported using the following syntax:

```shell
# Using a HCP Packer Bucket Resource Name
# packer/project/{project_id}/bucket/{bucket_name}
terraform import hcp_packer_bucket.alpine packer/project/f709ec73-55d4-46d8-897d-816ebba28778/bucket/alpine
```
13 changes: 13 additions & 0 deletions examples/guides/packer_bucket_rbac/iam_binding_resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "hcp_service_principal" "my-sp" {
name = "my-sp"
}

resource "hcp_packer_bucket" "production" {
name = "production"
}

resource "hcp_packer_bucket_iam_binding" "example" {
resource_name = hcp_packer_bucket.production.resource_name
principal_id = hcp_service_principal.my-sp.resource_id
role = "roles/contributor"
}
20 changes: 20 additions & 0 deletions examples/guides/packer_bucket_rbac/iam_policy_resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
data "hcp_iam_policy" "mypolicy" {
bindings = [
{
role = "roles/contributor"
principals = [
"user-principal-id-1",
"service-principal-id-1",
]
},
]
}

resource "hcp_packer_bucket" "production" {
name = "production"
}

resource "hcp_packer_bucket_iam_policy" "example" {
resource_name = hcp_packer_bucket.production.resource_name
policy_data = data.hcp_iam_policy.mypolicy.policy_data
}
13 changes: 13 additions & 0 deletions examples/resources/hcp_packer_bucket_iam_binding/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "hcp_service_principal" "my-sp" {
name = "my-sp"
}

resource "hcp_packer_bucket" "production" {
name = "production"
}

resource "hcp_packer_bucket_iam_binding" "example" {
resource_name = hcp_packer_bucket.production.resource_name
principal_id = hcp_service_principal.my-sp.resource_id
role = "roles/contributor"
}
4 changes: 4 additions & 0 deletions examples/resources/hcp_packer_bucket_iam_policy/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Using a HCP Packer Bucket Resource Name
# packer/project/{project_id}/bucket/{bucket_name}
terraform import hcp_packer_bucket.alpine packer/project/f709ec73-55d4-46d8-897d-816ebba28778/bucket/alpine

20 changes: 20 additions & 0 deletions examples/resources/hcp_packer_bucket_iam_policy/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
data "hcp_iam_policy" "mypolicy" {
bindings = [
{
role = "roles/contributor"
principals = [
"user-principal-id-1",
"service-principal-id-1",
]
},
]
}

resource "hcp_packer_bucket" "production" {
name = "production"
}

resource "hcp_packer_bucket_iam_policy" "example" {
resource_name = hcp_packer_bucket.production.resource_name
policy_data = data.hcp_iam_policy.mypolicy.policy_data
}
2 changes: 2 additions & 0 deletions internal/provider/packer/packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
// Framework provider. To add a new resource, add a new function to this list.
var ResourceSchemaBuilders []func() resource.Resource = []func() resource.Resource{
bucket.NewPackerBucketResource,
bucket.NewPackerBucketIAMPolicyResource,
bucket.NewPackerBucketAppIAMBindingResource,
}

// DataSourceSchemaBuilders is a list of all HCP Packer data sources exposed by the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package bucket

import (
"context"
"net/http"

"github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/resource_service"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/models"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-hcp/internal/clients"
"github.com/hashicorp/terraform-provider-hcp/internal/clients/iampolicy"
)

// packerBucketIAMSchema is the schema for the HCP Packer bucket resource IAM resources
// (policy/binding). It will be merged with the base policy.
func packerBucketIAMSchema(binding bool) schema.Schema {
// Determine the description based on if it is for the policy or binding
d := "Sets the HCP Packer Bucket IAM policy and replaces any existing policy."
if binding {
d = "Updates the HCP Packer Bucket IAM policy to bind a role to a new member. Existing bindings are preserved."
}

return schema.Schema{
MarkdownDescription: d,
Attributes: map[string]schema.Attribute{
"resource_name": schema.StringAttribute{
Required: true,
Description: "The bucket's resource name in the format packer/project/<project ID>/bucket/<bucket name>.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func NewPackerBucketIAMPolicyResource() resource.Resource {
return iampolicy.NewResourceIamPolicy("packer_bucket", packerBucketIAMSchema(false), "resource_name", newPackerBucketAppResourceIAMPolicyUpdater)
}

func NewPackerBucketAppIAMBindingResource() resource.Resource {
return iampolicy.NewResourceIamBinding("packer_bucket", packerBucketIAMSchema(true), "resource_name", newPackerBucketAppResourceIAMPolicyUpdater)
}

type packerBucketResourceIAMPolicyUpdater struct {
resourceName string
client *clients.Client
d iampolicy.TerraformResourceData
}

func newPackerBucketAppResourceIAMPolicyUpdater(
ctx context.Context,
d iampolicy.TerraformResourceData,
clients *clients.Client) (iampolicy.ResourceIamUpdater, diag.Diagnostics) {

var resourceName types.String
diags := d.GetAttribute(ctx, path.Root("resource_name"), &resourceName)

return &packerBucketResourceIAMPolicyUpdater{
resourceName: resourceName.ValueString(),
client: clients,
d: d,
}, diags
}

func (u *packerBucketResourceIAMPolicyUpdater) GetMutexKey() string {
return u.resourceName
}

// GetResourceIamPolicy Fetch the existing IAM policy attached to a resource.
func (u *packerBucketResourceIAMPolicyUpdater) GetResourceIamPolicy(ctx context.Context) (*models.HashicorpCloudResourcemanagerPolicy, diag.Diagnostics) {
var diags diag.Diagnostics
params := resource_service.NewResourceServiceGetIamPolicyParams()
params.ResourceName = &u.resourceName

res, err := u.client.ResourceService.ResourceServiceGetIamPolicy(params, nil)
if err != nil {
serviceErr, ok := err.(*resource_service.ResourceServiceGetIamPolicyDefault)
if !ok {
diags.AddError("failed to cast resource IAM policy error", err.Error())
return nil, diags
}
if serviceErr.Code() == http.StatusNotFound {
return &models.HashicorpCloudResourcemanagerPolicy{}, diags
}
diags.AddError("failed to retrieve resource IAM policy", err.Error())
return nil, diags
}

return res.GetPayload().Policy, diags
}

// SetResourceIamPolicy Replaces the existing IAM Policy attached to a resource.
func (u *packerBucketResourceIAMPolicyUpdater) SetResourceIamPolicy(ctx context.Context, policy *models.HashicorpCloudResourcemanagerPolicy) (*models.HashicorpCloudResourcemanagerPolicy, diag.Diagnostics) {
var diags diag.Diagnostics
params := resource_service.NewResourceServiceSetIamPolicyParams()

params.Body = &models.HashicorpCloudResourcemanagerResourceSetIamPolicyRequest{
Policy: policy,
ResourceName: u.resourceName,
}

res, err := u.client.ResourceService.ResourceServiceSetIamPolicy(params, nil)
if err != nil {
diags.AddError("failed to retrieve resource IAM policy", err.Error())
return nil, diags
}

return res.GetPayload().Policy, diags
}

var (
_ iampolicy.NewResourceIamUpdaterFunc = newPackerBucketAppResourceIAMPolicyUpdater
_ iampolicy.ResourceIamUpdater = &packerBucketResourceIAMPolicyUpdater{}
)
Loading

0 comments on commit 6a44b00

Please sign in to comment.