Skip to content

Commit

Permalink
Merge pull request crossplane-contrib#1735 from awetomaton/crossplane…
Browse files Browse the repository at this point in the history
…-contribgh-1080-permissionsboundary
  • Loading branch information
MisterMX authored Jul 28, 2023
2 parents 78c7005 + af8e198 commit d4f4788
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 60 deletions.
26 changes: 19 additions & 7 deletions pkg/clients/iam/fake/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ var _ clientset.RoleClient = (*MockRoleClient)(nil)

// MockRoleClient is a type that implements all the methods for RoleClient interface
type MockRoleClient struct {
MockGetRole func(ctx context.Context, input *iam.GetRoleInput, opts []func(*iam.Options)) (*iam.GetRoleOutput, error)
MockCreateRole func(ctx context.Context, input *iam.CreateRoleInput, opts []func(*iam.Options)) (*iam.CreateRoleOutput, error)
MockDeleteRole func(ctx context.Context, input *iam.DeleteRoleInput, opts []func(*iam.Options)) (*iam.DeleteRoleOutput, error)
MockUpdateRole func(ctx context.Context, input *iam.UpdateRoleInput, opts []func(*iam.Options)) (*iam.UpdateRoleOutput, error)
MockUpdateAssumeRolePolicy func(ctx context.Context, input *iam.UpdateAssumeRolePolicyInput, opts []func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error)
MockTagRole func(ctx context.Context, input *iam.TagRoleInput, opts []func(*iam.Options)) (*iam.TagRoleOutput, error)
MockUntagRole func(ctx context.Context, input *iam.UntagRoleInput, opts []func(*iam.Options)) (*iam.UntagRoleOutput, error)
MockGetRole func(ctx context.Context, input *iam.GetRoleInput, opts []func(*iam.Options)) (*iam.GetRoleOutput, error)
MockCreateRole func(ctx context.Context, input *iam.CreateRoleInput, opts []func(*iam.Options)) (*iam.CreateRoleOutput, error)
MockDeleteRole func(ctx context.Context, input *iam.DeleteRoleInput, opts []func(*iam.Options)) (*iam.DeleteRoleOutput, error)
MockUpdateRole func(ctx context.Context, input *iam.UpdateRoleInput, opts []func(*iam.Options)) (*iam.UpdateRoleOutput, error)
MockPutRolePermissionsBoundary func(ctx context.Context, input *iam.PutRolePermissionsBoundaryInput, opts []func(*iam.Options)) (*iam.PutRolePermissionsBoundaryOutput, error)
MockDeleteRolePermissionsBoundary func(ctx context.Context, input *iam.DeleteRolePermissionsBoundaryInput, opts []func(*iam.Options)) (*iam.DeleteRolePermissionsBoundaryOutput, error)
MockUpdateAssumeRolePolicy func(ctx context.Context, input *iam.UpdateAssumeRolePolicyInput, opts []func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error)
MockTagRole func(ctx context.Context, input *iam.TagRoleInput, opts []func(*iam.Options)) (*iam.TagRoleOutput, error)
MockUntagRole func(ctx context.Context, input *iam.UntagRoleInput, opts []func(*iam.Options)) (*iam.UntagRoleOutput, error)
}

// GetRole mocks GetRole method
Expand All @@ -58,6 +60,16 @@ func (m *MockRoleClient) UpdateRole(ctx context.Context, input *iam.UpdateRoleIn
return m.MockUpdateRole(ctx, input, opts)
}

// PutRolePermissionsBoundary mocks PutRolePermissionsBoundary method
func (m *MockRoleClient) PutRolePermissionsBoundary(ctx context.Context, input *iam.PutRolePermissionsBoundaryInput, opts ...func(*iam.Options)) (*iam.PutRolePermissionsBoundaryOutput, error) {
return m.MockPutRolePermissionsBoundary(ctx, input, opts)
}

// DeleteRolePermissionsBoundary mocks DeleteRolePermissionsBoundary method
func (m *MockRoleClient) DeleteRolePermissionsBoundary(ctx context.Context, input *iam.DeleteRolePermissionsBoundaryInput, opts ...func(*iam.Options)) (*iam.DeleteRolePermissionsBoundaryOutput, error) {
return m.MockDeleteRolePermissionsBoundary(ctx, input, opts)
}

// UpdateAssumeRolePolicy mocks UpdateAssumeRolePolicy method
func (m *MockRoleClient) UpdateAssumeRolePolicy(ctx context.Context, input *iam.UpdateAssumeRolePolicyInput, opts ...func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error) {
return m.MockUpdateAssumeRolePolicy(ctx, input, opts)
Expand Down
26 changes: 19 additions & 7 deletions pkg/clients/iam/fake/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ type MockUserInput struct {

// MockUserClient is a type that implements all the methods for RoleClient interface
type MockUserClient struct {
MockUserInput MockUserInput
MockGetUser func(ctx context.Context, input *iam.GetUserInput, opts []func(*iam.Options)) (*iam.GetUserOutput, error)
MockCreateUser func(ctx context.Context, input *iam.CreateUserInput, opts []func(*iam.Options)) (*iam.CreateUserOutput, error)
MockDeleteUser func(ctx context.Context, input *iam.DeleteUserInput, opts []func(*iam.Options)) (*iam.DeleteUserOutput, error)
MockUpdateUser func(ctx context.Context, input *iam.UpdateUserInput, opts []func(*iam.Options)) (*iam.UpdateUserOutput, error)
MockTagUser func(ctx context.Context, input *iam.TagUserInput, opt []func(*iam.Options)) (*iam.TagUserOutput, error)
MockUntagUser func(ctx context.Context, input *iam.UntagUserInput, opts []func(*iam.Options)) (*iam.UntagUserOutput, error)
MockUserInput MockUserInput
MockGetUser func(ctx context.Context, input *iam.GetUserInput, opts []func(*iam.Options)) (*iam.GetUserOutput, error)
MockCreateUser func(ctx context.Context, input *iam.CreateUserInput, opts []func(*iam.Options)) (*iam.CreateUserOutput, error)
MockDeleteUser func(ctx context.Context, input *iam.DeleteUserInput, opts []func(*iam.Options)) (*iam.DeleteUserOutput, error)
MockUpdateUser func(ctx context.Context, input *iam.UpdateUserInput, opts []func(*iam.Options)) (*iam.UpdateUserOutput, error)
MockPutUserPermissionsBoundary func(ctx context.Context, input *iam.PutUserPermissionsBoundaryInput, opts []func(*iam.Options)) (*iam.PutUserPermissionsBoundaryOutput, error)
MockDeleteUserPermissionsBoundary func(ctx context.Context, input *iam.DeleteUserPermissionsBoundaryInput, opts []func(*iam.Options)) (*iam.DeleteUserPermissionsBoundaryOutput, error)
MockTagUser func(ctx context.Context, input *iam.TagUserInput, opt []func(*iam.Options)) (*iam.TagUserOutput, error)
MockUntagUser func(ctx context.Context, input *iam.UntagUserInput, opts []func(*iam.Options)) (*iam.UntagUserOutput, error)
}

// GetUser mocks GetUser method
Expand All @@ -64,6 +66,16 @@ func (m *MockUserClient) UpdateUser(ctx context.Context, input *iam.UpdateUserIn
return m.MockUpdateUser(ctx, input, opts)
}

// PutUserPermissionsBoundary mocks PutUserPermissionsBoundary method
func (m *MockUserClient) PutUserPermissionsBoundary(ctx context.Context, input *iam.PutUserPermissionsBoundaryInput, opts ...func(*iam.Options)) (*iam.PutUserPermissionsBoundaryOutput, error) {
return m.MockPutUserPermissionsBoundary(ctx, input, opts)
}

// DeleteUserPermissionsBoundary mocks DeleteUserPermissionsBoundary method
func (m *MockUserClient) DeleteUserPermissionsBoundary(ctx context.Context, input *iam.DeleteUserPermissionsBoundaryInput, opts ...func(*iam.Options)) (*iam.DeleteUserPermissionsBoundaryOutput, error) {
return m.MockDeleteUserPermissionsBoundary(ctx, input, opts)
}

// TagUser mocks TagUser method
func (m *MockUserClient) TagUser(ctx context.Context, input *iam.TagUserInput, opts ...func(*iam.Options)) (*iam.TagUserOutput, error) {
m.MockUserInput.TagUserInput = input
Expand Down
7 changes: 7 additions & 0 deletions pkg/clients/iam/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type RoleClient interface {
CreateRole(ctx context.Context, input *iam.CreateRoleInput, opts ...func(*iam.Options)) (*iam.CreateRoleOutput, error)
DeleteRole(ctx context.Context, input *iam.DeleteRoleInput, opts ...func(*iam.Options)) (*iam.DeleteRoleOutput, error)
UpdateRole(ctx context.Context, input *iam.UpdateRoleInput, opts ...func(*iam.Options)) (*iam.UpdateRoleOutput, error)
PutRolePermissionsBoundary(ctx context.Context, params *iam.PutRolePermissionsBoundaryInput, optFns ...func(*iam.Options)) (*iam.PutRolePermissionsBoundaryOutput, error)
DeleteRolePermissionsBoundary(ctx context.Context, params *iam.DeleteRolePermissionsBoundaryInput, optFns ...func(*iam.Options)) (*iam.DeleteRolePermissionsBoundaryOutput, error)
UpdateAssumeRolePolicy(ctx context.Context, input *iam.UpdateAssumeRolePolicyInput, opts ...func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error)
TagRole(ctx context.Context, input *iam.TagRoleInput, opts ...func(*iam.Options)) (*iam.TagRoleOutput, error)
UntagRole(ctx context.Context, input *iam.UntagRoleInput, opts ...func(*iam.Options)) (*iam.UntagRoleOutput, error)
Expand Down Expand Up @@ -89,6 +91,11 @@ func GenerateRole(in v1beta1.RoleParameters, role *iamtypes.Role) error {
role.Description = in.Description
role.MaxSessionDuration = in.MaxSessionDuration
role.Path = in.Path
if in.PermissionsBoundary != nil {
role.PermissionsBoundary = &iamtypes.AttachedPermissionsBoundary{
PermissionsBoundaryArn: in.PermissionsBoundary,
}
}

if len(in.Tags) != 0 {
role.Tags = make([]iamtypes.Tag, len(in.Tags))
Expand Down
2 changes: 2 additions & 0 deletions pkg/clients/iam/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type UserClient interface {
CreateUser(ctx context.Context, input *iam.CreateUserInput, opts ...func(*iam.Options)) (*iam.CreateUserOutput, error)
DeleteUser(ctx context.Context, input *iam.DeleteUserInput, opts ...func(*iam.Options)) (*iam.DeleteUserOutput, error)
UpdateUser(ctx context.Context, input *iam.UpdateUserInput, opts ...func(*iam.Options)) (*iam.UpdateUserOutput, error)
PutUserPermissionsBoundary(ctx context.Context, params *iam.PutUserPermissionsBoundaryInput, optFns ...func(*iam.Options)) (*iam.PutUserPermissionsBoundaryOutput, error)
DeleteUserPermissionsBoundary(ctx context.Context, params *iam.DeleteUserPermissionsBoundaryInput, optFns ...func(*iam.Options)) (*iam.DeleteUserPermissionsBoundaryOutput, error)
TagUser(ctx context.Context, params *iam.TagUserInput, opts ...func(*iam.Options)) (*iam.TagUserOutput, error)
UntagUser(ctx context.Context, params *iam.UntagUserInput, opts ...func(*iam.Options)) (*iam.UntagUserOutput, error)
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/controller/iam/role/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,27 @@ func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.Ex
}
}

boundaryArn := ""
if observed.Role.PermissionsBoundary != nil {
boundaryArn = *observed.Role.PermissionsBoundary.PermissionsBoundaryArn
}
if aws.ToString(&boundaryArn) != aws.ToString(cr.Spec.ForProvider.PermissionsBoundary) {
if aws.ToString(cr.Spec.ForProvider.PermissionsBoundary) == "" {
_, err = e.client.DeleteRolePermissionsBoundary(ctx, &awsiam.DeleteRolePermissionsBoundaryInput{
RoleName: aws.String(meta.GetExternalName(cr)),
})
} else {
_, err = e.client.PutRolePermissionsBoundary(ctx, &awsiam.PutRolePermissionsBoundaryInput{
PermissionsBoundary: cr.Spec.ForProvider.PermissionsBoundary,
RoleName: aws.String(meta.GetExternalName(cr)),
})
}

if err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errUpdate)
}
}

if patch.AssumeRolePolicyDocument != "" {
_, err = e.client.UpdateAssumeRolePolicy(ctx, &awsiam.UpdateAssumeRolePolicyInput{
PolicyDocument: &cr.Spec.ForProvider.AssumeRolePolicyDocument,
Expand Down
155 changes: 112 additions & 43 deletions pkg/controller/iam/user/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/aws/aws-sdk-go-v2/aws"
awsiam "github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"

Expand All @@ -45,13 +46,15 @@ import (
const (
errUnexpectedObject = "The managed resource is not an IAM User resource"

errGet = "cannot get IAM User"
errCreate = "cannot create the IAM User resource"
errDelete = "cannot delete the IAM User resource"
errUpdate = "cannot update the IAM User resource"
errSDK = "empty IAM User received from IAM API"
errTag = "cannot tag the IAM User resource"
errUntag = "cannot remove tags from the IAM User resource"
errGet = "cannot get IAM User"
errCreate = "cannot create the IAM User resource"
errDelete = "cannot delete the IAM User resource"
errUpdateUser = "cannot update the IAM User resource"
errPutUserPermissionsBoundary = "cannot update the IAM User permission boundary"
errDeleteUserPermissionsBoundary = "cannot delete the IAM User permission boundary"
errSDK = "empty IAM User received from IAM API"
errTag = "cannot tag the IAM User resource"
errUntag = "cannot remove tags from the IAM User resource"

errKubeUpdateFailed = "cannot late initialize IAM User"
)
Expand Down Expand Up @@ -135,16 +138,9 @@ func (e *external) Observe(ctx context.Context, mgd resource.Managed) (managed.E
UserID: aws.ToString(user.UserId),
}

crTagMap := make(map[string]string, len(cr.Spec.ForProvider.Tags))
for _, v := range cr.Spec.ForProvider.Tags {
crTagMap[v.Key] = v.Value
}
_, _, areTagsUpdated := iam.DiffIAMTags(crTagMap, observed.User.Tags)

return managed.ExternalObservation{
ResourceExists: true,
ResourceUpToDate: aws.ToString(cr.Spec.ForProvider.Path) == aws.ToString(user.Path) &&
areTagsUpdated,
ResourceExists: true,
ResourceUpToDate: isUpToDate(cr, &user),
}, nil
}

Expand All @@ -171,44 +167,29 @@ func (e *external) Update(ctx context.Context, mgd resource.Managed) (managed.Ex
return managed.ExternalUpdate{}, errors.New(errUnexpectedObject)
}

_, err := e.client.UpdateUser(ctx, &awsiam.UpdateUserInput{
NewPath: cr.Spec.ForProvider.Path,
UserName: aws.String(meta.GetExternalName(cr)),
})

if err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errUpdate)
}

observed, err := e.client.GetUser(ctx, &awsiam.GetUserInput{
UserName: aws.String(meta.GetExternalName(cr)),
})

if err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errGet)
return managed.ExternalUpdate{}, awsclient.Wrap(resource.Ignore(iam.IsErrorNotFound, err), errGet)
}

add, remove, _ := iam.DiffIAMTagsWithUpdates(cr.Spec.ForProvider.Tags, observed.User.Tags)

if len(add) > 0 {
if _, err := e.client.TagUser(ctx, &awsiam.TagUserInput{
UserName: aws.String(meta.GetExternalName(cr)),
Tags: add,
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errTag)
}
// take care of changes to path (only call if necessary)
err = e.updateUser(ctx, observed, cr)
if err != nil {
return managed.ExternalUpdate{}, err
}

if len(remove) > 0 {
if _, err := e.client.UntagUser(ctx, &awsiam.UntagUserInput{
TagKeys: remove,
UserName: aws.String(meta.GetExternalName(cr)),
}); err != nil {
return managed.ExternalUpdate{}, awsclient.Wrap(err, errUntag)
}
// take care of changes to PermissionBoundary (only call if necessary)
err = e.updatePermissionsBoundary(ctx, observed, cr)
if err != nil {
return managed.ExternalUpdate{}, err
}

return managed.ExternalUpdate{}, awsclient.Wrap(err, errUpdate)
// take care of changes to Tags
err = e.updateTags(ctx, observed, cr)
return managed.ExternalUpdate{}, err
}

func (e *external) Delete(ctx context.Context, mgd resource.Managed) error {
Expand Down Expand Up @@ -258,3 +239,91 @@ func (t *tagger) Initialize(ctx context.Context, mgd resource.Managed) error {
}
return errors.Wrap(t.kube.Update(ctx, cr), errKubeUpdateFailed)
}

func (e *external) updateUser(ctx context.Context, observed *awsiam.GetUserOutput, cr *v1beta1.User) error {
if aws.ToString(observed.User.Path) != aws.ToString(cr.Spec.ForProvider.Path) {
_, err := e.client.UpdateUser(ctx, &awsiam.UpdateUserInput{
NewPath: cr.Spec.ForProvider.Path,
UserName: aws.String(meta.GetExternalName(cr)),
})

return awsclient.Wrap(err, errUpdateUser)
}

return nil
}

func (e *external) updatePermissionsBoundary(ctx context.Context, observed *awsiam.GetUserOutput, cr *v1beta1.User) error {
boundaryArn := ""
var err error

if observed.User.PermissionsBoundary != nil {
boundaryArn = *observed.User.PermissionsBoundary.PermissionsBoundaryArn
}
if aws.ToString(&boundaryArn) != aws.ToString(cr.Spec.ForProvider.PermissionsBoundary) {
// is this a delete?
if aws.ToString(cr.Spec.ForProvider.PermissionsBoundary) == "" {
_, err = e.client.DeleteUserPermissionsBoundary(ctx, &awsiam.DeleteUserPermissionsBoundaryInput{
UserName: aws.String(meta.GetExternalName(cr)),
})

return awsclient.Wrap(err, errDeleteUserPermissionsBoundary)
}

// must be an update
_, err = e.client.PutUserPermissionsBoundary(ctx, &awsiam.PutUserPermissionsBoundaryInput{
PermissionsBoundary: cr.Spec.ForProvider.PermissionsBoundary,
UserName: aws.String(meta.GetExternalName(cr)),
})

return awsclient.Wrap(err, errPutUserPermissionsBoundary)
}

return nil
}

func (e *external) updateTags(ctx context.Context, observed *awsiam.GetUserOutput, cr *v1beta1.User) error {
add, remove, _ := iam.DiffIAMTagsWithUpdates(cr.Spec.ForProvider.Tags, observed.User.Tags)

if len(add) > 0 {
if _, err := e.client.TagUser(ctx, &awsiam.TagUserInput{
UserName: aws.String(meta.GetExternalName(cr)),
Tags: add,
}); err != nil {
return awsclient.Wrap(err, errTag)
}
}

if len(remove) > 0 {
if _, err := e.client.UntagUser(ctx, &awsiam.UntagUserInput{
TagKeys: remove,
UserName: aws.String(meta.GetExternalName(cr)),
}); err != nil {
return awsclient.Wrap(err, errUntag)
}
}

return nil
}

func isUpToDate(cr *v1beta1.User, user *types.User) bool {
// check path
isPathUpdated := aws.ToString(cr.Spec.ForProvider.Path) == aws.ToString(user.Path)

// check tags
crTagMap := make(map[string]string, len(cr.Spec.ForProvider.Tags))
for _, v := range cr.Spec.ForProvider.Tags {
crTagMap[v.Key] = v.Value
}
_, _, areTagsUpdated := iam.DiffIAMTags(crTagMap, user.Tags)

// check permissions boundary
boundaryArn := ""
if user.PermissionsBoundary != nil {
boundaryArn = *user.PermissionsBoundary.PermissionsBoundaryArn
}
isBoundaryUpdated :=
aws.ToString(cr.Spec.ForProvider.PermissionsBoundary) == aws.ToString(&boundaryArn)

return isPathUpdated && areTagsUpdated && isBoundaryUpdated
}
Loading

0 comments on commit d4f4788

Please sign in to comment.