diff --git a/lib/auth/integration/integrationv1/awsra.go b/lib/auth/integration/integrationv1/awsra.go index 5a7ba6373dbd3..ff12884ea66c3 100644 --- a/lib/auth/integration/integrationv1/awsra.go +++ b/lib/auth/integration/integrationv1/awsra.go @@ -232,6 +232,7 @@ func (s *AWSRolesAnywhereService) ListRolesAnywhereProfiles(ctx context.Context, profileList, err := awsra.ListRolesAnywhereProfiles(ctx, listRolesAnywhereClient, awsra.ListRolesAnywhereProfilesRequest{ NextToken: req.GetNextPageToken(), ProfileNameFilters: req.GetProfileNameFilters(), + IgnoredProfileARNs: []string{spec.ProfileSyncConfig.ProfileARN}, PageSize: int(req.GetPageSize()), }) if err != nil { @@ -321,7 +322,9 @@ func (s *AWSRolesAnywhereService) AWSRolesAnywherePing(ctx context.Context, req return nil, trace.Wrap(err) } - pingResp, err := awsra.Ping(ctx, pingClient, profileARN) + ignoredProfiles := []string{profileARN} + + pingResp, err := awsra.Ping(ctx, pingClient, ignoredProfiles) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/integrations/awsra/list_rolesanywhere_profiles.go b/lib/integrations/awsra/list_rolesanywhere_profiles.go index d86f169e99dd4..9da6d1150662e 100644 --- a/lib/integrations/awsra/list_rolesanywhere_profiles.go +++ b/lib/integrations/awsra/list_rolesanywhere_profiles.go @@ -20,6 +20,7 @@ package awsra import ( "context" + "slices" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" @@ -50,6 +51,8 @@ type ListRolesAnywhereProfilesRequest struct { // ^profile.*$ // ^.*name.*$ ProfileNameFilters []string + // IgnoredProfileARNs is a list of profile ARNs that should be ignored. + IgnoredProfileARNs []string } // ListRolesAnywhereProfilesResponse contains a page of Roles Anywhere Profiles. @@ -97,9 +100,10 @@ func ListRolesAnywhereProfiles(ctx context.Context, clt RolesAnywhereProfilesLis nextToken = aws.String(req.NextToken) } listReq := listRolesAnywhereProfilesRequest{ - nextPage: nextToken, - pageSize: req.PageSize, - filters: req.ProfileNameFilters, + nextPage: nextToken, + pageSize: req.PageSize, + filters: req.ProfileNameFilters, + ignoredProfileARNs: req.IgnoredProfileARNs, } profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, listReq) if err != nil { @@ -113,9 +117,10 @@ func ListRolesAnywhereProfiles(ctx context.Context, clt RolesAnywhereProfilesLis } type listRolesAnywhereProfilesRequest struct { - nextPage *string - pageSize int - filters []string + nextPage *string + pageSize int + filters []string + ignoredProfileARNs []string } func listRolesAnywhereProfilesPage(ctx context.Context, raClient RolesAnywhereProfilesLister, req listRolesAnywhereProfilesRequest) (ret []*integrationv1.RolesAnywhereProfile, nextToken *string, err error) { @@ -157,6 +162,10 @@ func convertRolesAnywhereProfilePageToProto(ctx context.Context, profilesListRes var ret []*integrationv1.RolesAnywhereProfile for _, profile := range profilesListResp.Profiles { + if slices.Contains(req.ignoredProfileARNs, aws.ToString(profile.ProfileArn)) { + continue + } + matches, err := profileNameMatchesFilters(aws.ToString(profile.Name), req.filters) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/integrations/awsra/list_rolesanywhere_profiles_test.go b/lib/integrations/awsra/list_rolesanywhere_profiles_test.go index 2a55dd5761cd9..534686e961ed9 100644 --- a/lib/integrations/awsra/list_rolesanywhere_profiles_test.go +++ b/lib/integrations/awsra/list_rolesanywhere_profiles_test.go @@ -25,6 +25,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" ratypes "github.com/aws/aws-sdk-go-v2/service/rolesanywhere/types" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -67,12 +69,15 @@ func TestListRolesAnywhereProfiles(t *testing.T) { } func TestListRolesAnywhereProfilesPage(t *testing.T) { - rolesAnywhereProfileWithName := func(name string) ratypes.ProfileDetail { + rolesAnywhereProfileWithNameAndARN := func(name string, profileARN string) ratypes.ProfileDetail { return ratypes.ProfileDetail{ Name: aws.String(name), - ProfileArn: aws.String(uuid.NewString()), + ProfileArn: aws.String(profileARN), } } + rolesAnywhereProfileWithName := func(name string) ratypes.ProfileDetail { + return rolesAnywhereProfileWithNameAndARN(name, uuid.NewString()) + } for _, tt := range []struct { name string @@ -160,6 +165,36 @@ func TestListRolesAnywhereProfilesPage(t *testing.T) { }, expectedErrCheck: require.NoError, }, + { + name: "ignoring the sync profile", + req: listRolesAnywhereProfilesRequest{ + ignoredProfileARNs: []string{"arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id1"}, + }, + clientMock: &mockIAMRolesAnywhere{ + profilesByID: map[string]ratypes.ProfileDetail{ + "id1": rolesAnywhereProfileWithNameAndARN("TeamA-subteam1", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id1"), + "id2": rolesAnywhereProfileWithNameAndARN("TeamA-subteam2", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id2"), + "id3": rolesAnywhereProfileWithNameAndARN("TeamA-subteam3", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id3"), + "id4": rolesAnywhereProfileWithNameAndARN("TeamB-subteam1", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id4"), + "id5": rolesAnywhereProfileWithNameAndARN("TeamB-subteam2", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id5"), + "id6": rolesAnywhereProfileWithNameAndARN("TeamB-subteam3", "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id6"), + }, + }, + expectedResp: func(t *testing.T, page []*integrationv1.RolesAnywhereProfile) { + require.Len(t, page, 5) + cmpOpts := []cmp.Option{ + cmpopts.IgnoreUnexported(integrationv1.RolesAnywhereProfile{}), + } + require.Empty(t, cmp.Diff(page, []*integrationv1.RolesAnywhereProfile{ + {Arn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id2", Name: "TeamA-subteam2", Tags: make(map[string]string)}, + {Arn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id3", Name: "TeamA-subteam3", Tags: make(map[string]string)}, + {Arn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id4", Name: "TeamB-subteam1", Tags: make(map[string]string)}, + {Arn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id5", Name: "TeamB-subteam2", Tags: make(map[string]string)}, + {Arn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/id6", Name: "TeamB-subteam3", Tags: make(map[string]string)}, + }, cmpOpts...)) + }, + expectedErrCheck: require.NoError, + }, } { t.Run(tt.name, func(t *testing.T) { resp, _, err := listRolesAnywhereProfilesPage(t.Context(), tt.clientMock, tt.req) diff --git a/lib/integrations/awsra/ping.go b/lib/integrations/awsra/ping.go index fcc12438f1502..148e6a6cd0083 100644 --- a/lib/integrations/awsra/ping.go +++ b/lib/integrations/awsra/ping.go @@ -77,14 +77,15 @@ func NewPingClient(ctx context.Context, req *AWSClientConfig) (PingClient, error // It returns a list of Roles Anywhere Profiles that are enabled. // // It will ignore any profile matching ignoredProfileARN. -func Ping(ctx context.Context, clt PingClient, ignoredProfileARN string) (*PingResponse, error) { +func Ping(ctx context.Context, clt PingClient, ignoredProfileARNs []string) (*PingResponse, error) { var errs []error profileCounter := 0 var nextToken *string for { listReq := listRolesAnywhereProfilesRequest{ - nextPage: nextToken, + nextPage: nextToken, + ignoredProfileARNs: ignoredProfileARNs, } profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, listReq) if err != nil { @@ -92,11 +93,8 @@ func Ping(ctx context.Context, clt PingClient, ignoredProfileARN string) (*PingR break } for _, profile := range profiles { - // Ignore disabled profiles, profiles without assigned roles and the ignoreProfileARN. - if profile.Enabled && - len(profile.Roles) > 0 && - profile.Arn != ignoredProfileARN { - + // Ignore disabled profiles and profiles without assigned roles. + if profile.Enabled && len(profile.Roles) > 0 { profileCounter++ } } diff --git a/lib/integrations/awsra/ping_test.go b/lib/integrations/awsra/ping_test.go index f499c7667e9d9..8525173a974c8 100644 --- a/lib/integrations/awsra/ping_test.go +++ b/lib/integrations/awsra/ping_test.go @@ -75,7 +75,7 @@ func TestPing(t *testing.T) { disabledProfile, profileWithoutRoles, }, - }, *syncProfile.ProfileArn) + }, []string{*syncProfile.ProfileArn}) require.NoError(t, err) require.Equal(t, "123456789012", resp.AccountID) diff --git a/lib/integrations/awsra/profile_syncer.go b/lib/integrations/awsra/profile_syncer.go index fd389c5b93b3d..0a4e11970ff6d 100644 --- a/lib/integrations/awsra/profile_syncer.go +++ b/lib/integrations/awsra/profile_syncer.go @@ -433,12 +433,14 @@ func syncProfileForIntegration(ctx context.Context, params AWSRolesAnywhereProfi } profileNameFilters := integration.GetAWSRolesAnywhereIntegrationSpec().ProfileSyncConfig.ProfileNameFilters + profileUsedForProfileSync := integration.GetAWSRolesAnywhereIntegrationSpec().ProfileSyncConfig.ProfileARN var nextPage *string for { listReq := listRolesAnywhereProfilesRequest{ - nextPage: nextPage, - filters: profileNameFilters, + nextPage: nextPage, + filters: profileNameFilters, + ignoredProfileARNs: []string{profileUsedForProfileSync}, } profilesListResp, respNextToken, err := listRolesAnywhereProfilesPage(ctx, raClient, listReq) if err != nil { @@ -455,7 +457,7 @@ func syncProfileForIntegration(ctx context.Context, params AWSRolesAnywhereProfi ProxyPublicAddr: proxyPublicAddr, }) if err != nil { - if errors.Is(err, errDisabledProfile) || errors.Is(err, errProfileIsUsedForSync) { + if errors.Is(err, errDisabledProfile) { logger.DebugContext(ctx, "Skipping profile", "profile_name", profile.Name, "error", err.Error()) continue } @@ -478,8 +480,7 @@ func syncProfileForIntegration(ctx context.Context, params AWSRolesAnywhereProfi } var ( - errDisabledProfile = errors.New("profile is disabled") - errProfileIsUsedForSync = errors.New("profile is used to sync profiles and will not be added as an aws app") + errDisabledProfile = errors.New("profile is disabled") ) type processProfileRequest struct { @@ -491,12 +492,6 @@ type processProfileRequest struct { } func processProfile(ctx context.Context, req processProfileRequest) error { - profileSyncProfileARN := req.Integration.GetAWSRolesAnywhereIntegrationSpec().ProfileSyncConfig.ProfileARN - - if req.Profile.Arn == profileSyncProfileARN { - return errProfileIsUsedForSync - } - if !req.Profile.Enabled { return errDisabledProfile } diff --git a/lib/integrations/awsra/trustanchor_config_test.go b/lib/integrations/awsra/trustanchor_config_test.go index 560543cf1375b..80efc0088f206 100644 --- a/lib/integrations/awsra/trustanchor_config_test.go +++ b/lib/integrations/awsra/trustanchor_config_test.go @@ -25,6 +25,7 @@ import ( "maps" "os" "slices" + "strings" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -347,8 +348,14 @@ func (m *mockIAMRolesAnywhere) ListProfiles(ctx context.Context, params *rolesan if m.profilesByID == nil { m.profilesByID = make(map[string]ratypes.ProfileDetail) } + + allProfiles := slices.Collect(maps.Values(m.profilesByID)) + slices.SortFunc(allProfiles, func(a, b ratypes.ProfileDetail) int { + return strings.Compare(aws.ToString(a.ProfileArn), aws.ToString(b.ProfileArn)) + }) + return &rolesanywhere.ListProfilesOutput{ - Profiles: slices.Collect(maps.Values(m.profilesByID)), + Profiles: allProfiles, }, nil } @@ -422,7 +429,6 @@ func (m *mockIAMRoles) CreateRole(ctx context.Context, params *iam.CreateRoleInp Message: &alreadyExistsMessage, } } - fmt.Println("===========Creating role:", *params.RoleName) m.existingRoles[*params.RoleName] = mockRole{ tags: params.Tags, assumeRolePolicyDoc: params.AssumeRolePolicyDocument,