From fff9016a0679a2e31a05679adcdaa527305b1be1 Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Wed, 3 Jul 2024 22:44:09 +0900 Subject: [PATCH] extract stale policy and new policy --- internal/aws/aws.go | 1 + internal/aws/aws_role.go | 40 ++++++++++++- internal/aws/aws_role_test.go | 108 ++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 internal/aws/aws_role_test.go diff --git a/internal/aws/aws.go b/internal/aws/aws.go index fbc429d..9556a13 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -27,6 +27,7 @@ type AwsIamAPI interface { DeleteOpenIDConnectProvider(ctx context.Context, params *iam.DeleteOpenIDConnectProviderInput, optFns ...func(*iam.Options)) (*iam.DeleteOpenIDConnectProviderOutput, error) CreateRole(ctx context.Context, params *iam.CreateRoleInput, optFns ...func(*iam.Options)) (*iam.CreateRoleOutput, error) UpdateAssumeRolePolicy(ctx context.Context, params *iam.UpdateAssumeRolePolicyInput, optFns ...func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error) + ListAttachedRolePolicies(ctx context.Context, params *iam.ListAttachedRolePoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) AttachRolePolicy(ctx context.Context, params *iam.AttachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.AttachRolePolicyOutput, error) DeleteRole(ctx context.Context, params *iam.DeleteRoleInput, optFns ...func(*iam.Options)) (*iam.DeleteRoleOutput, error) DetachRolePolicy(ctx context.Context, params *iam.DetachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.DetachRolePolicyOutput, error) diff --git a/internal/aws/aws_role.go b/internal/aws/aws_role.go index fd472c7..73933bd 100644 --- a/internal/aws/aws_role.go +++ b/internal/aws/aws_role.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/smithy-go" irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" "github.com/kkb0318/irsa-manager/internal/issuer" @@ -29,6 +30,7 @@ type RoleManager struct { AccountId string } +// PolicyArn returns the full ARN of a given policy name. If the policy name already has the full ARN, it returns it as is. func (r *RoleManager) PolicyArn(policy string) *string { prefix := "arn:aws:iam::" if strings.HasPrefix(policy, prefix) { @@ -37,6 +39,33 @@ func (r *RoleManager) PolicyArn(policy string) *string { return aws.String(fmt.Sprintf("%saws:policy/%s", prefix, policy)) } +// ExtractNewPolicies returns the names of the policies that are in the current settings (r.Policies) but are not yet attached to the role. + +func (r *RoleManager) ExtractNewPolicies(l *iam.ListAttachedRolePoliciesOutput) []string { + result := []string{} + for _, p := range r.Policies { + if slices.ContainsFunc(l.AttachedPolicies, func(a types.AttachedPolicy) bool { + return *r.PolicyArn(p) != *a.PolicyArn + }) { + result = append(result, p) + } + } + return result +} + +// ExtractStalePolicies returns the ARNs of the policies that are attached to the role but are not in the current settings (r.Policies). +func (r *RoleManager) ExtractStalePolicies(l *iam.ListAttachedRolePoliciesOutput) []string { + result := []string{} + for _, ap := range l.AttachedPolicies { + if slices.ContainsFunc(r.Policies, func(p string) bool { + return *r.PolicyArn(p) != *ap.PolicyArn + }) { + result = append(result, *ap.PolicyArn) + } + } + return result +} + // DeleteIRSARole detaches specified policies from the IAM role and deletes the IAM role func (a *AwsIamClient) DeleteIRSARole(ctx context.Context, r RoleManager) error { for _, policy := range r.Policies { @@ -93,7 +122,7 @@ func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerMeta issuer.OID AssumeRolePolicyDocument: aws.String(string(trustPolicyJSON)), } - _, err = a.Client.CreateRole(context.TODO(), createRoleInput) + _, err = a.Client.CreateRole(ctx, createRoleInput) if errorHandler(err, []string{"EntityAlreadyExists"}) != nil { return err } @@ -104,11 +133,16 @@ func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerMeta issuer.OID PolicyDocument: aws.String(string(trustPolicyJSON)), } - _, err = a.Client.UpdateAssumeRolePolicy(context.TODO(), updateRoleInput) + _, err = a.Client.UpdateAssumeRolePolicy(ctx, updateRoleInput) if err != nil { return fmt.Errorf("failed to update assume role policy for role %s: %w", r.RoleName, err) } log.Printf("Assume role policy for %s updated successfully", r.RoleName) + // TODO: + // listPoliciesOutput, err := a.Client.ListAttachedRolePolicies(ctx, &iam.ListAttachedRolePoliciesInput{RoleName: aws.String(r.RoleName)}) + // if err != nil { + // return fmt.Errorf("failed to list attached role policies with %s: %w", r.RoleName, err) + // } for _, policy := range r.Policies { attachRolePolicyInput := &iam.AttachRolePolicyInput{ @@ -116,7 +150,7 @@ func (a *AwsIamClient) CreateIRSARole(ctx context.Context, issuerMeta issuer.OID PolicyArn: r.PolicyArn(policy), } - _, err = a.Client.AttachRolePolicy(context.TODO(), attachRolePolicyInput) + _, err = a.Client.AttachRolePolicy(ctx, attachRolePolicyInput) if err != nil { return err } diff --git a/internal/aws/aws_role_test.go b/internal/aws/aws_role_test.go new file mode 100644 index 0000000..7e01366 --- /dev/null +++ b/internal/aws/aws_role_test.go @@ -0,0 +1,108 @@ +package aws + +import ( + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/stretchr/testify/assert" +) + +func TestExtractNewPolicies(t *testing.T) { + tests := []struct { + name string + policies []string + attachedPolicies *iam.ListAttachedRolePoliciesOutput + expected []string + }{ + { + "PolicyAlreadyAttached", + []string{"ReadOnlyAccess", "AdministratorAccess"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/ReadOnlyAccess")}, + }, + }, + []string{"AdministratorAccess"}, + }, + { + "NoPolicyAttached", + []string{"PowerUserAccess", "SecurityAudit"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/ReadOnlyAccess")}, + }, + }, + []string{"PowerUserAccess", "SecurityAudit"}, + }, + { + "AllPoliciesAlreadyAttached", + []string{"ReadOnlyAccess"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/ReadOnlyAccess")}, + }, + }, + []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &RoleManager{Policies: tt.policies} + result := r.ExtractNewPolicies(tt.attachedPolicies) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestExtractStalePolicies(t *testing.T) { + tests := []struct { + name string + policies []string + attachedPolicies *iam.ListAttachedRolePoliciesOutput + expected []string + }{ + { + "StalePolicyExists", + []string{"ReadOnlyAccess"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/ReadOnlyAccess")}, + {PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess")}, + }, + }, + []string{"arn:aws:iam::aws:policy/AdministratorAccess"}, + }, + { + "MultipleStalePoliciesExist", + []string{"ReadOnlyAccess", "SecurityAudit"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/PowerUserAccess")}, + {PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess")}, + }, + }, + []string{"arn:aws:iam::aws:policy/PowerUserAccess", "arn:aws:iam::aws:policy/AdministratorAccess"}, + }, + { + "NoStalePolicies", + []string{"ReadOnlyAccess"}, + &iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: []types.AttachedPolicy{ + {PolicyArn: aws.String("arn:aws:iam::aws:policy/ReadOnlyAccess")}, + }, + }, + []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &RoleManager{Policies: tt.policies} + result := r.ExtractStalePolicies(tt.attachedPolicies) + assert.Equal(t, tt.expected, result) + }) + } +}