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

feat: add grants to roles #14

Merged
merged 2 commits into from
Jul 12, 2020
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/hashicorp/terraform-plugin-sdk v1.14.0
github.com/hashicorp/terraform-svchost v0.0.0-20191119180714-d2e4933b9136 // indirect
github.com/hashicorp/vault-plugin-secrets-ad v0.6.5 // indirect
github.com/hashicorp/watchtower v0.0.0-20200710194626-bcdcaffa6f4a
github.com/hashicorp/watchtower v0.0.0-20200711004400-e1afed75ffd5
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.1.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,10 @@ github.com/hashicorp/watchtower v0.0.0-20200710171123-5302e6d7fff8 h1:4938moh+be
github.com/hashicorp/watchtower v0.0.0-20200710171123-5302e6d7fff8/go.mod h1:y22wYAss3hsYe/BtE0p7xSH1NeZQp8UKhpbTA8tVOl0=
github.com/hashicorp/watchtower v0.0.0-20200710194626-bcdcaffa6f4a h1:Veaf8DQz7J074kP0NHP0pzWWDwsNicytPQwb8FMVAgM=
github.com/hashicorp/watchtower v0.0.0-20200710194626-bcdcaffa6f4a/go.mod h1:y22wYAss3hsYe/BtE0p7xSH1NeZQp8UKhpbTA8tVOl0=
github.com/hashicorp/watchtower v0.0.0-20200710222414-9bd0a888f487 h1:xChhkSVM8bXb43eiETcmJ0xsHbAB8H9UbEwrgy6OQNA=
github.com/hashicorp/watchtower v0.0.0-20200710222414-9bd0a888f487/go.mod h1:y22wYAss3hsYe/BtE0p7xSH1NeZQp8UKhpbTA8tVOl0=
github.com/hashicorp/watchtower v0.0.0-20200711004400-e1afed75ffd5 h1:PPTHChAtcKstP2ouEqlRWu/BSPtgsqL9nOgFFRSnwTo=
github.com/hashicorp/watchtower v0.0.0-20200711004400-e1afed75ffd5/go.mod h1:y22wYAss3hsYe/BtE0p7xSH1NeZQp8UKhpbTA8tVOl0=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
Expand Down
69 changes: 61 additions & 8 deletions internal/provider/resource_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func resourceRole() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
roleGrantsKey: {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}

Expand Down Expand Up @@ -72,6 +77,12 @@ func convertRoleToResourceData(u *roles.Role, d *schema.ResourceData) error {
}
}

if u.Grants != nil {
if err := d.Set(roleGrantsKey, u.Grants); err != nil {
return err
}
}

d.SetId(u.Id)

return nil
Expand Down Expand Up @@ -100,6 +111,12 @@ func convertResourceDataToRole(d *schema.ResourceData) *roles.Role {
u.UserIds = append(u.UserIds, i.(string))
}
}
if val, ok := d.GetOk(roleGrantsKey); ok {
grants := val.(*schema.Set).List()
for _, i := range grants {
u.Grants = append(u.Grants, i.(string))
}
}

if d.Id() != "" {
u.Id = d.Id()
Expand All @@ -120,16 +137,36 @@ func resourceRoleCreate(d *schema.ResourceData, meta interface{}) error {
r := convertResourceDataToRole(d)
users := r.UserIds
groups := r.GroupIds
grants := r.Grants

r, apiErr, err := o.CreateRole(ctx, r)
if apiErr != nil || err != nil {
return fmt.Errorf("error creating role:\n API Err: %v\n Err: %v\n", *apiErr.Message, err)
newRole, apiErr, err := o.CreateRole(ctx, r)
if apiErr != nil {
return fmt.Errorf("error creating role: %s\n", *apiErr.Message)
}
if err != nil {
return fmt.Errorf("error creating role: %s\n", err)
}

// on first create CreateRole() returns without err but upon
// running AddGrants it claims the role is not found. This
// doesn't occur in the test case but only on a live cluster.
if len(grants) > 0 {
r, apiErr, err = newRole.AddGrants(ctx, grants)
if apiErr != nil {
return fmt.Errorf("error setting grants on role:: %s\n", *apiErr.Message)
}
if err != nil {
return fmt.Errorf("error setting grants on role: %s\n", err)
}
}

if len(users) > 0 || len(groups) > 0 {
r, apiErr, err = r.SetPrincipals(ctx, groups, users)
if apiErr != nil || err != nil {
return fmt.Errorf("error setting principle on role:\n API Err: %+v\n Err: %+v\n", *apiErr.Message, err)
if apiErr != nil {
return fmt.Errorf("error setting principle on role: %s\n", *apiErr.Message)
}
if err != nil {
return fmt.Errorf("error setting principle on role: %s\n", err)
}
}

Expand Down Expand Up @@ -183,10 +220,26 @@ func resourceRoleUpdate(d *schema.ResourceData, meta interface{}) error {

r.GroupIds = nil
r.UserIds = nil
r.Grants = nil

r, apiErr, err := o.UpdateRole(ctx, r)
if apiErr != nil || err != nil {
return fmt.Errorf("error updating role:\n API Err: %v\n Err: %v\n", *apiErr.Message, err)
return fmt.Errorf("error updating role:\n API Err: %+v\n Err: %+v\n", *apiErr, err)
}

grants := []string{}
if d.HasChange(roleGrantsKey) {
grantSet := d.Get(roleGrantsKey).(*schema.Set).List()
for _, grant := range grantSet {
grants = append(grants, grant.(string))
}
}

if d.HasChange(roleGrantsKey) {
_, apiErr, err := r.SetGrants(ctx, grants)
if apiErr != nil || err != nil {
return fmt.Errorf("error setting grants on role:\n API Err: %+v\n Err: %+v\n", *apiErr, err)
}
}

userIDs := []string{}
Expand All @@ -208,7 +261,7 @@ func resourceRoleUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange(roleGroupsKey) || d.HasChange(roleUsersKey) {
r, apiErr, err = r.SetPrincipals(ctx, groupIDs, userIDs)
if apiErr != nil || err != nil {
return fmt.Errorf("error updating principle on role:\n API Err: %v\n Err: %v\n", *apiErr.Message, err)
return fmt.Errorf("error updating principle on role:\n API Err: %+v\n Err: %+v\n", *apiErr, err)
}
}

Expand All @@ -228,7 +281,7 @@ func resourceRoleDelete(d *schema.ResourceData, meta interface{}) error {

_, apiErr, err := o.DeleteRole(ctx, r)
if apiErr != nil || err != nil {
return fmt.Errorf("error deleting role:\n API Err: %v\n Err: %v\n", *apiErr.Message, err)
return fmt.Errorf("error deleting role:\n API Err: %+v\n Err: %+v\n", *apiErr, err)
}

return nil
Expand Down
97 changes: 95 additions & 2 deletions internal/provider/resource_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,56 @@ resource "watchtower_role" "foo" {
description = "test description"
groups = [watchtower_group.foo.id, watchtower_group.bar.id]
}`

readonlyGrant = "id=*;actions=read"
readonlyGrantUpdate = "id=*;actions=read,create"
fooRoleWithGrants = fmt.Sprintf(`
resource "watchtower_role" "foo" {
name = "readonly"
description = "test description"
grants = ["%s"]
}`, readonlyGrant)
fooRoleWithGrantsUpdate = fmt.Sprintf(`
resource "watchtower_role" "foo" {
name = "readonly"
description = "test description"
grants = ["%s", "%s"]
}`, readonlyGrant, readonlyGrantUpdate)
)

func TestAccRoleWithGrants(t *testing.T) {
tc := controller.NewTestController(t, controller.WithDefaultOrgId("o_0000000000"))
defer tc.Shutdown()
url := tc.ApiAddrs()[0]

resource.Test(t, resource.TestCase{
Providers: testProviders,
CheckDestroy: testAccCheckRoleResourceDestroy(t),
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooRoleWithGrants),
Check: resource.ComposeTestCheckFunc(
testAccCheckRoleResourceExists("watchtower_role.foo"),
testAccCheckRoleResourceGrantsSet("watchtower_role.foo", []string{readonlyGrant}),
resource.TestCheckResourceAttr("watchtower_role.foo", "name", "readonly"),
resource.TestCheckResourceAttr("watchtower_role.foo", "description", "test description"),
),
},
{
// test update
Config: testConfig(url, fooRoleWithGrantsUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckRoleResourceExists("watchtower_role.foo"),
testAccCheckRoleResourceGrantsSet("watchtower_role.foo", []string{readonlyGrant, readonlyGrantUpdate}),
resource.TestCheckResourceAttr("watchtower_role.foo", "name", "readonly"),
resource.TestCheckResourceAttr("watchtower_role.foo", "description", "test description"),
),
},
},
})
}

func TestAccRoleWithUsers(t *testing.T) {
tc := controller.NewTestController(t, controller.WithDefaultOrgId("o_0000000000"))
defer tc.Shutdown()
Expand Down Expand Up @@ -311,7 +359,6 @@ func testAccCheckRoleResourceUsersSet(name string, users []string) resource.Test
ok := false
for _, gotUser := range userIDs {
if gotUser == stateUser {
fmt.Printf("[Debug] user found in WT: %s, %s", gotUser, stateUser)
ok = true
}
}
Expand Down Expand Up @@ -374,7 +421,6 @@ func testAccCheckRoleResourceGroupsSet(name string, groups []string) resource.Te
ok := false
for _, gotGroup := range groupIDs {
if gotGroup == stateGroup {
fmt.Printf("[Debug] group found in WT: %s, %s", gotGroup, stateGroup)
ok = true
}
}
Expand All @@ -386,6 +432,53 @@ func testAccCheckRoleResourceGroupsSet(name string, groups []string) resource.Te
return nil
}
}

func testAccCheckRoleResourceGrantsSet(name string, expectedGrants []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("role resource not found: %s", name)
}

id := rs.Primary.ID
if id == "" {
return fmt.Errorf("role resource ID is not set")
}

md := testProvider.Meta().(*metaData)

u := roles.Role{Id: id}

o := &scopes.Organization{
Client: md.client,
}

r, _, err := o.ReadRole(md.ctx, &u)
if err != nil {
return fmt.Errorf("Got an error when reading role %q: %v", id, err)
}

// for each expected grant, ensure they're set on the role
if len(r.Grants) == 0 {
return fmt.Errorf("no grants found on role, %+v\n", r)
}

for _, grant := range expectedGrants {
ok = false
for _, gotGrant := range r.Grants {
if gotGrant == grant {
ok = true
}
}
if !ok {
return fmt.Errorf("expected grant not found on role, %s: %s\n Have: %v\n", *r.Name, grant, r)
}
}

return nil
}
}

func testAccCheckRoleResourceDestroy(t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
md := testProvider.Meta().(*metaData)
Expand Down
19 changes: 18 additions & 1 deletion website/docs/r/resource_role.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,24 @@ resource "watchtower_user" "bar" {
resource "watchtower_role" "example" {
name = "My role"
description = "My first role!"
users [watchtower_user.foo.id, watchtower_user.bar.id]
users = [watchtower_user.foo.id, watchtower_user.bar.id]
}

```

Usage with user and grants resource:

```hcl
resource "watchtower_user" "readonly" {
name = "readonly"
description = "A readonly user"
}

resource "watchtower_role" "readonly" {
name = "readonly"
description = "A readonly role"
users = [watchtower_user.readonly.id]
grants = ["id=*;action=read"]
}
```

Expand Down