mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
AWS Roles Anywhere: implement profile filters (sync and list profiles method) (#56718)
* AWS IAM Roles Anywhere: implement filters for Profile sync and lister * move client out of request * improve godoc for the ListRolesAnywhereProfiles api
This commit is contained in:
@@ -263,7 +263,8 @@ type ListRolesAnywhereProfilesRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Integration is the AWS Roles Anywhere Integration name.
|
||||
Integration string `protobuf:"bytes,1,opt,name=integration,proto3" json:"integration,omitempty"`
|
||||
// page_size is the size of the page to request.
|
||||
// page_size is the max size of the page to request.
|
||||
// Depending on the filters, the actual number of profiles returned may be less than this value.
|
||||
PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
||||
// next_page_token is the page token.
|
||||
NextPageToken string `protobuf:"bytes,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
|
||||
|
||||
@@ -53,6 +53,9 @@ type AWSRolesAnywhereServiceClient interface {
|
||||
// It uses the following API:
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListProfiles.html
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListTagsForResource.html
|
||||
//
|
||||
// The number of profiles returned is always between 0 and page_size.
|
||||
// If the number of elements is 0, then there are no more profiles to return and the next page token is empty.
|
||||
ListRolesAnywhereProfiles(ctx context.Context, in *ListRolesAnywhereProfilesRequest, opts ...grpc.CallOption) (*ListRolesAnywhereProfilesResponse, error)
|
||||
}
|
||||
|
||||
@@ -100,6 +103,9 @@ type AWSRolesAnywhereServiceServer interface {
|
||||
// It uses the following API:
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListProfiles.html
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListTagsForResource.html
|
||||
//
|
||||
// The number of profiles returned is always between 0 and page_size.
|
||||
// If the number of elements is 0, then there are no more profiles to return and the next page token is empty.
|
||||
ListRolesAnywhereProfiles(context.Context, *ListRolesAnywhereProfilesRequest) (*ListRolesAnywhereProfilesResponse, error)
|
||||
mustEmbedUnimplementedAWSRolesAnywhereServiceServer()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ service AWSRolesAnywhereService {
|
||||
// It uses the following API:
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListProfiles.html
|
||||
// https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ListTagsForResource.html
|
||||
//
|
||||
// The number of profiles returned is always between 0 and page_size.
|
||||
// If the number of elements is 0, then there are no more profiles to return and the next page token is empty.
|
||||
rpc ListRolesAnywhereProfiles(ListRolesAnywhereProfilesRequest) returns (ListRolesAnywhereProfilesResponse);
|
||||
}
|
||||
|
||||
@@ -73,7 +76,8 @@ message AWSRolesAnywherePingResponse {
|
||||
message ListRolesAnywhereProfilesRequest {
|
||||
// Integration is the AWS Roles Anywhere Integration name.
|
||||
string integration = 1;
|
||||
// page_size is the size of the page to request.
|
||||
// page_size is the max size of the page to request.
|
||||
// Depending on the filters, the actual number of profiles returned may be less than this value.
|
||||
int32 page_size = 2;
|
||||
// next_page_token is the page token.
|
||||
string next_page_token = 3;
|
||||
|
||||
@@ -230,7 +230,9 @@ func (s *AWSRolesAnywhereService) ListRolesAnywhereProfiles(ctx context.Context,
|
||||
}
|
||||
|
||||
profileList, err := awsra.ListRolesAnywhereProfiles(ctx, listRolesAnywhereClient, awsra.ListRolesAnywhereProfilesRequest{
|
||||
NextToken: req.GetNextPageToken(),
|
||||
NextToken: req.GetNextPageToken(),
|
||||
ProfileNameFilters: req.GetProfileNameFilters(),
|
||||
PageSize: int(req.GetPageSize()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
@@ -100,6 +100,70 @@ func TestGenerateAWSRACredentials(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWSRolesAnywhereProfileSyncFilters(t *testing.T) {
|
||||
t.Parallel()
|
||||
clusterName := "test-cluster"
|
||||
proxyPublicAddr := "example.com:443"
|
||||
|
||||
ca := newCertAuthority(t, types.AWSRACA, clusterName)
|
||||
ctx, localClient, resourceSvc := initSvc(t, ca, clusterName, proxyPublicAddr)
|
||||
|
||||
ctx = authorizerForDummyUser(t, ctx, types.RoleSpecV6{
|
||||
Allow: types.RoleConditions{Rules: []types.Rule{
|
||||
{Resources: []string{types.KindIntegration}, Verbs: []string{types.VerbCreate, types.VerbUpdate}},
|
||||
}},
|
||||
}, localClient)
|
||||
|
||||
integrationWithFilters := func(t *testing.T, integrationName string, filters []string) *types.IntegrationV1 {
|
||||
ig, err := types.NewIntegrationAWSRA(
|
||||
types.Metadata{Name: integrationName},
|
||||
&types.AWSRAIntegrationSpecV1{
|
||||
TrustAnchorARN: "arn:aws:rolesanywhere:eu-west-2:123456789012:trust-anchor/12345678-1234-1234-1234-123456789012",
|
||||
ProfileSyncConfig: &types.AWSRolesAnywhereProfileSyncConfig{
|
||||
Enabled: true,
|
||||
ProfileARN: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/12345678-1234-1234-1234-123456789012",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/SyncRole",
|
||||
ProfileNameFilters: filters,
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return ig
|
||||
}
|
||||
|
||||
t.Run("valid without filters", func(t *testing.T) {
|
||||
ig := integrationWithFilters(t, "integration1", []string{})
|
||||
_, err := resourceSvc.CreateIntegration(ctx, &integrationv1.CreateIntegrationRequest{
|
||||
Integration: ig,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("valid with glob filter", func(t *testing.T) {
|
||||
ig := integrationWithFilters(t, "integration2", []string{"MyTeam-*"})
|
||||
_, err := resourceSvc.CreateIntegration(ctx, &integrationv1.CreateIntegrationRequest{
|
||||
Integration: ig,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("valid with regex filter", func(t *testing.T) {
|
||||
ig := integrationWithFilters(t, "integration3", []string{"^MyTeam-.*$"})
|
||||
_, err := resourceSvc.CreateIntegration(ctx, &integrationv1.CreateIntegrationRequest{
|
||||
Integration: ig,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid with invalid regex", func(t *testing.T) {
|
||||
ig := integrationWithFilters(t, "integration5", []string{`^[invalid-regex{$`})
|
||||
_, err := resourceSvc.CreateIntegration(ctx, &integrationv1.CreateIntegrationRequest{
|
||||
Integration: ig,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWSRolesAnywherePing(t *testing.T) {
|
||||
t.Parallel()
|
||||
clusterName := "test-cluster"
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/gravitational/teleport/lib/integrations/awsra/createsession"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// Cache is the subset of the cached resources that the Service queries.
|
||||
@@ -254,6 +255,10 @@ func (s *Service) CreateIntegration(ctx context.Context, req *integrationpb.Crea
|
||||
if errs := validation.IsDNS1035Label(req.GetIntegration().GetName()); len(errs) > 0 {
|
||||
return nil, trace.BadParameter("integration name %q must be a lower case valid DNS subdomain so that it can be used to allow Web/CLI access", req.GetIntegration().GetName())
|
||||
}
|
||||
|
||||
if err := validateAWSRolesAnywhereProfileFilters(req.Integration); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
ig, err := s.backend.CreateIntegration(ctx, req.Integration)
|
||||
@@ -302,6 +307,10 @@ func (s *Service) UpdateIntegration(ctx context.Context, req *integrationpb.Upda
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := validateAWSRolesAnywhereProfileFilters(req.Integration); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := s.maybeUpdateStaticCredentials(ctx, req.Integration); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -341,6 +350,24 @@ func (s *Service) UpdateIntegration(ctx context.Context, req *integrationpb.Upda
|
||||
return igV1, nil
|
||||
}
|
||||
|
||||
func validateAWSRolesAnywhereProfileFilters(ig types.Integration) error {
|
||||
rolesAnywhereSpec := ig.GetAWSRolesAnywhereIntegrationSpec()
|
||||
if rolesAnywhereSpec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rolesAnywhereSpec.ProfileSyncConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, profileNameFilter := range rolesAnywhereSpec.ProfileSyncConfig.ProfileNameFilters {
|
||||
if _, err := utils.CompileExpression(profileNameFilter); err != nil {
|
||||
return trace.BadParameter("invalid filter %q, use glob-like matching or regex by adding the anchors (eg, ^regex$): %v", profileNameFilter, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteIntegration removes the specified Integration resource.
|
||||
//
|
||||
// This RPC may remove multiple resources in the backend:
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// ListRolesAnywhereProfilesRequest contains the required fields to list the Roles Anywhere Profiles in Amazon IAM.
|
||||
@@ -35,6 +36,20 @@ type ListRolesAnywhereProfilesRequest struct {
|
||||
NextToken string
|
||||
// PageSize is the maximum number of records to retrieve.
|
||||
PageSize int
|
||||
// ProfileNameFilters is a list of filters applied to the profile name.
|
||||
// Only matching profiles will be synchronized as application servers.
|
||||
// If empty, no filtering is applied.
|
||||
//
|
||||
// Filters can be globs, for example:
|
||||
//
|
||||
// profile*
|
||||
// *name*
|
||||
//
|
||||
// Or regexes if they're prefixed and suffixed with ^ and $, for example:
|
||||
//
|
||||
// ^profile.*$
|
||||
// ^.*name.*$
|
||||
ProfileNameFilters []string
|
||||
}
|
||||
|
||||
// ListRolesAnywhereProfilesResponse contains a page of Roles Anywhere Profiles.
|
||||
@@ -81,7 +96,12 @@ func ListRolesAnywhereProfiles(ctx context.Context, clt RolesAnywhereProfilesLis
|
||||
if req.NextToken != "" {
|
||||
nextToken = aws.String(req.NextToken)
|
||||
}
|
||||
profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, nextToken, req.PageSize)
|
||||
listReq := listRolesAnywhereProfilesRequest{
|
||||
nextPage: nextToken,
|
||||
pageSize: req.PageSize,
|
||||
filters: req.ProfileNameFilters,
|
||||
}
|
||||
profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, listReq)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -92,28 +112,64 @@ func ListRolesAnywhereProfiles(ctx context.Context, clt RolesAnywhereProfilesLis
|
||||
}, nil
|
||||
}
|
||||
|
||||
func listRolesAnywhereProfilesPage(ctx context.Context, raClient RolesAnywhereProfilesLister, nextPage *string, pageSize int) ([]*integrationv1.RolesAnywhereProfile, *string, error) {
|
||||
type listRolesAnywhereProfilesRequest struct {
|
||||
nextPage *string
|
||||
pageSize int
|
||||
filters []string
|
||||
}
|
||||
|
||||
func listRolesAnywhereProfilesPage(ctx context.Context, raClient RolesAnywhereProfilesLister, req listRolesAnywhereProfilesRequest) (ret []*integrationv1.RolesAnywhereProfile, nextToken *string, err error) {
|
||||
var pageSizeRequest *int32
|
||||
if req.pageSize > 0 {
|
||||
pageSizeRequest = aws.Int32(int32(req.pageSize))
|
||||
}
|
||||
|
||||
nextPage := req.nextPage
|
||||
|
||||
for {
|
||||
profilesListResp, err := raClient.ListProfiles(ctx, &rolesanywhere.ListProfilesInput{
|
||||
NextToken: nextPage,
|
||||
PageSize: pageSizeRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
profilesAsProto, err := convertRolesAnywhereProfilePageToProto(ctx, profilesListResp, raClient, req)
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
ret = append(ret, profilesAsProto...)
|
||||
|
||||
// If no profile was added (because of filters), and there's more resources, fetch the next page.
|
||||
// This ensures that the client doesn't receive an empty page when there are more resources to fetch.
|
||||
nextPage = profilesListResp.NextToken
|
||||
if len(ret) == 0 && aws.ToString(nextPage) != "" {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return ret, nextPage, nil
|
||||
}
|
||||
|
||||
func convertRolesAnywhereProfilePageToProto(ctx context.Context, profilesListResp *rolesanywhere.ListProfilesOutput, raClient RolesAnywhereProfilesLister, req listRolesAnywhereProfilesRequest) ([]*integrationv1.RolesAnywhereProfile, error) {
|
||||
var ret []*integrationv1.RolesAnywhereProfile
|
||||
|
||||
var pageSizeRequest *int32
|
||||
if pageSize > 0 {
|
||||
pageSizeRequest = aws.Int32(int32(pageSize))
|
||||
}
|
||||
|
||||
profilesListResp, err := raClient.ListProfiles(ctx, &rolesanywhere.ListProfilesInput{
|
||||
NextToken: nextPage,
|
||||
PageSize: pageSizeRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
for _, profile := range profilesListResp.Profiles {
|
||||
matches, err := profileNameMatchesFilters(aws.ToString(profile.Name), req.filters)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
profileTags, err := raClient.ListTagsForResource(ctx, &rolesanywhere.ListTagsForResourceInput{
|
||||
ResourceArn: profile.ProfileArn,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
labels := make(map[string]string, len(profileTags.Tags))
|
||||
@@ -131,5 +187,23 @@ func listRolesAnywhereProfilesPage(ctx context.Context, raClient RolesAnywherePr
|
||||
})
|
||||
}
|
||||
|
||||
return ret, profilesListResp.NextToken, nil
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func profileNameMatchesFilters(profileName string, filters []string) (bool, error) {
|
||||
// If no filters are provided, all profiles match.
|
||||
if len(filters) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, filter := range filters {
|
||||
matches, err := utils.MatchString(profileName, filter)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err, "error parsing filter: %s", filter)
|
||||
}
|
||||
if matches {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ 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/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
|
||||
)
|
||||
|
||||
func TestListRolesAnywhereProfiles(t *testing.T) {
|
||||
@@ -63,6 +66,109 @@ func TestListRolesAnywhereProfiles(t *testing.T) {
|
||||
require.Len(t, resp.Profiles, 3)
|
||||
}
|
||||
|
||||
func TestListRolesAnywhereProfilesPage(t *testing.T) {
|
||||
rolesAnywhereProfileWithName := func(name string) ratypes.ProfileDetail {
|
||||
return ratypes.ProfileDetail{
|
||||
Name: aws.String(name),
|
||||
ProfileArn: aws.String(uuid.NewString()),
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
req listRolesAnywhereProfilesRequest
|
||||
clientMock RolesAnywhereProfilesLister
|
||||
expectedResp func(t *testing.T, page []*integrationv1.RolesAnywhereProfile)
|
||||
expectedErrCheck require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "no resources",
|
||||
req: listRolesAnywhereProfilesRequest{},
|
||||
clientMock: &mockIAMRolesAnywhere{},
|
||||
expectedResp: func(t *testing.T, page []*integrationv1.RolesAnywhereProfile) {
|
||||
require.Empty(t, page)
|
||||
},
|
||||
expectedErrCheck: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "some resources",
|
||||
req: listRolesAnywhereProfilesRequest{},
|
||||
clientMock: &mockIAMRolesAnywhere{
|
||||
profilesByID: map[string]ratypes.ProfileDetail{
|
||||
"id1": rolesAnywhereProfileWithName("ExampleProfile"),
|
||||
},
|
||||
},
|
||||
expectedResp: func(t *testing.T, page []*integrationv1.RolesAnywhereProfile) {
|
||||
require.Len(t, page, 1)
|
||||
require.Equal(t, "ExampleProfile", page[0].Name)
|
||||
},
|
||||
expectedErrCheck: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "with filters using glob",
|
||||
req: listRolesAnywhereProfilesRequest{
|
||||
filters: []string{"TeamB-*"},
|
||||
},
|
||||
clientMock: &mockIAMRolesAnywhere{
|
||||
profilesByID: map[string]ratypes.ProfileDetail{
|
||||
"id1": rolesAnywhereProfileWithName("TeamA-subteam1"),
|
||||
"id2": rolesAnywhereProfileWithName("TeamA-subteam2"),
|
||||
"id3": rolesAnywhereProfileWithName("TeamA-subteam3"),
|
||||
|
||||
"id4": rolesAnywhereProfileWithName("TeamB-subteam1"),
|
||||
"id5": rolesAnywhereProfileWithName("TeamB-subteam2"),
|
||||
"id6": rolesAnywhereProfileWithName("TeamB-subteam3"),
|
||||
},
|
||||
},
|
||||
expectedResp: func(t *testing.T, page []*integrationv1.RolesAnywhereProfile) {
|
||||
require.Len(t, page, 3)
|
||||
profile := page[0]
|
||||
require.NotEmpty(t, profile.Arn)
|
||||
require.Contains(t, profile.Name, "TeamB-subteam")
|
||||
},
|
||||
expectedErrCheck: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "with filters using regex",
|
||||
req: listRolesAnywhereProfilesRequest{
|
||||
filters: []string{`^Team[A,B]{1}\-subteam\d$`},
|
||||
},
|
||||
clientMock: &mockIAMRolesAnywhere{
|
||||
profilesByID: map[string]ratypes.ProfileDetail{
|
||||
"id1": rolesAnywhereProfileWithName("TeamA-subteam1"),
|
||||
"id2": rolesAnywhereProfileWithName("TeamA-subteam2"),
|
||||
"id3": rolesAnywhereProfileWithName("TeamA-subteam3"),
|
||||
|
||||
"id4": rolesAnywhereProfileWithName("TeamB-subteam1"),
|
||||
"id5": rolesAnywhereProfileWithName("TeamB-subteam2"),
|
||||
"id6": rolesAnywhereProfileWithName("TeamB-subteam3"),
|
||||
|
||||
"id7": rolesAnywhereProfileWithName("TeamC-subteam1"),
|
||||
"id8": rolesAnywhereProfileWithName("TeamC-subteam2"),
|
||||
"id9": rolesAnywhereProfileWithName("TeamC-subteam3"),
|
||||
|
||||
"id10": rolesAnywhereProfileWithName("AnotherTeam"),
|
||||
},
|
||||
},
|
||||
expectedResp: func(t *testing.T, page []*integrationv1.RolesAnywhereProfile) {
|
||||
require.Len(t, page, 6)
|
||||
profileNames := make([]string, 0, len(page))
|
||||
for _, profile := range page {
|
||||
profileNames = append(profileNames, profile.Name)
|
||||
}
|
||||
require.Contains(t, profileNames, "TeamA-subteam1")
|
||||
},
|
||||
expectedErrCheck: require.NoError,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, _, err := listRolesAnywhereProfilesPage(t.Context(), tt.clientMock, tt.req)
|
||||
tt.expectedErrCheck(t, err)
|
||||
tt.expectedResp(t, resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockListRolesAnywhereProfiles struct {
|
||||
profiles []ratypes.ProfileDetail
|
||||
}
|
||||
|
||||
@@ -80,8 +80,10 @@ func Ping(ctx context.Context, clt PingClient) (*PingResponse, error) {
|
||||
profileCounter := 0
|
||||
var nextToken *string
|
||||
for {
|
||||
const useAPIDefaultPageSize = 0
|
||||
profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, nextToken, useAPIDefaultPageSize)
|
||||
listReq := listRolesAnywhereProfilesRequest{
|
||||
nextPage: nextToken,
|
||||
}
|
||||
profiles, nextPageToken, err := listRolesAnywhereProfilesPage(ctx, clt, listReq)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
break
|
||||
|
||||
@@ -388,10 +388,15 @@ func syncProfileForIntegration(ctx context.Context, params AWSRolesAnywherProfil
|
||||
return ret
|
||||
}
|
||||
|
||||
profileNameFilters := integration.GetAWSRolesAnywhereIntegrationSpec().ProfileSyncConfig.ProfileNameFilters
|
||||
|
||||
var nextPage *string
|
||||
for {
|
||||
const useAPIDefaultPageSize = 0
|
||||
profilesListResp, respNextToken, err := listRolesAnywhereProfilesPage(ctx, raClient, nextPage, useAPIDefaultPageSize)
|
||||
listReq := listRolesAnywhereProfilesRequest{
|
||||
nextPage: nextPage,
|
||||
filters: profileNameFilters,
|
||||
}
|
||||
profilesListResp, respNextToken, err := listRolesAnywhereProfilesPage(ctx, raClient, listReq)
|
||||
if err != nil {
|
||||
ret.setupError = trace.Wrap(err)
|
||||
return ret
|
||||
|
||||
@@ -110,9 +110,10 @@ func (h *Handler) integrationsCreate(w http.ResponseWriter, r *http.Request, p h
|
||||
}, &types.AWSRAIntegrationSpecV1{
|
||||
TrustAnchorARN: req.Integration.AWSRA.TrustAnchorARN,
|
||||
ProfileSyncConfig: &types.AWSRolesAnywhereProfileSyncConfig{
|
||||
Enabled: req.Integration.AWSRA.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: req.Integration.AWSRA.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: req.Integration.AWSRA.ProfileSyncConfig.RoleARN,
|
||||
Enabled: req.Integration.AWSRA.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: req.Integration.AWSRA.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: req.Integration.AWSRA.ProfileSyncConfig.RoleARN,
|
||||
ProfileNameFilters: req.Integration.AWSRA.ProfileSyncConfig.ProfileNameFilters,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -212,9 +213,10 @@ func (h *Handler) integrationsUpdate(w http.ResponseWriter, r *http.Request, p h
|
||||
spec := integration.GetAWSRolesAnywhereIntegrationSpec()
|
||||
spec.TrustAnchorARN = req.AWSRA.TrustAnchorARN
|
||||
spec.ProfileSyncConfig = &types.AWSRolesAnywhereProfileSyncConfig{
|
||||
Enabled: req.AWSRA.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: req.AWSRA.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: req.AWSRA.ProfileSyncConfig.RoleARN,
|
||||
Enabled: req.AWSRA.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: req.AWSRA.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: req.AWSRA.ProfileSyncConfig.RoleARN,
|
||||
ProfileNameFilters: req.AWSRA.ProfileSyncConfig.ProfileNameFilters,
|
||||
|
||||
ProfileAcceptsRoleSessionName: spec.ProfileSyncConfig.ProfileAcceptsRoleSessionName,
|
||||
}
|
||||
|
||||
@@ -223,8 +223,9 @@ func (h *Handler) awsRolesAnywhereListProfiles(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
listResp, err := clt.IntegrationAWSRolesAnywhereClient().ListRolesAnywhereProfiles(ctx, &integrationv1.ListRolesAnywhereProfilesRequest{
|
||||
Integration: integrationName,
|
||||
NextPageToken: req.StartKey,
|
||||
Integration: integrationName,
|
||||
NextPageToken: req.StartKey,
|
||||
ProfileNameFilters: req.Filters,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
@@ -171,9 +171,10 @@ func TestIntegrationsCRUDRolesAnywhere(t *testing.T) {
|
||||
AWSRA: &ui.IntegrationAWSRASpec{
|
||||
TrustAnchorARN: updatedTrustAnchor,
|
||||
ProfileSyncConfig: ui.AWSRAProfileSync{
|
||||
Enabled: true,
|
||||
ProfileARN: syncProfileARN,
|
||||
RoleARN: syncRoleARN,
|
||||
Enabled: true,
|
||||
ProfileARN: syncProfileARN,
|
||||
RoleARN: syncRoleARN,
|
||||
ProfileNameFilters: []string{"ExposedProfile-*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -197,6 +198,8 @@ func TestIntegrationsCRUDRolesAnywhere(t *testing.T) {
|
||||
require.True(t, integrationObject.AWSRA.ProfileSyncConfig.Enabled)
|
||||
require.Equal(t, syncProfileARN, integrationObject.AWSRA.ProfileSyncConfig.ProfileARN)
|
||||
require.Equal(t, syncRoleARN, integrationObject.AWSRA.ProfileSyncConfig.RoleARN)
|
||||
require.Len(t, integrationObject.AWSRA.ProfileSyncConfig.ProfileNameFilters, 1)
|
||||
require.Equal(t, "ExposedProfile-*", integrationObject.AWSRA.ProfileSyncConfig.ProfileNameFilters[0])
|
||||
|
||||
// Delete Integration
|
||||
err = wPack.server.Auth().DeleteIntegration(ctx, integrationName)
|
||||
|
||||
@@ -68,6 +68,21 @@ type AWSRAProfileSync struct {
|
||||
|
||||
// RoleARN is the ARN of the IAM Role that is used to sync profiles.
|
||||
RoleARN string `json:"roleArn"`
|
||||
|
||||
// ProfileNameFilters are the filters applied to the profiles.
|
||||
// Only matching profiles will be synchronized as application servers.
|
||||
// If empty, no filtering is applied.
|
||||
//
|
||||
// Filters can be globs, for example:
|
||||
//
|
||||
// profile*
|
||||
// *name*
|
||||
//
|
||||
// Or regexes if they're prefixed and suffixed with ^ and $, for example:
|
||||
//
|
||||
// ^profile.*$
|
||||
// ^.*name.*$
|
||||
ProfileNameFilters []string `json:"filters"`
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults for the aws oidc integration spec.
|
||||
@@ -355,9 +370,10 @@ func MakeIntegration(ig types.Integration) (*Integration, error) {
|
||||
ret.AWSRA = &IntegrationAWSRASpec{
|
||||
TrustAnchorARN: spec.TrustAnchorARN,
|
||||
ProfileSyncConfig: AWSRAProfileSync{
|
||||
Enabled: spec.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: spec.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: spec.ProfileSyncConfig.RoleARN,
|
||||
Enabled: spec.ProfileSyncConfig.Enabled,
|
||||
ProfileARN: spec.ProfileSyncConfig.ProfileARN,
|
||||
RoleARN: spec.ProfileSyncConfig.RoleARN,
|
||||
ProfileNameFilters: spec.ProfileSyncConfig.ProfileNameFilters,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -699,4 +715,19 @@ type AWSRolesAnywhereListProfilesRequest struct {
|
||||
// StartKey is the token to be used to fetch the next page.
|
||||
// If empty, the first page is fetched.
|
||||
StartKey string `json:"startKey"`
|
||||
|
||||
// Filters are the filters applied to the profiles.
|
||||
// Only matching profiles will be synchronized as application servers.
|
||||
// If empty, no filtering is applied.
|
||||
//
|
||||
// Filters can be globs, for example:
|
||||
//
|
||||
// profile*
|
||||
// *name*
|
||||
//
|
||||
// Or regexes if they're prefixed and suffixed with ^ and $, for example:
|
||||
//
|
||||
// ^profile.*$
|
||||
// ^.*name.*$
|
||||
Filters []string `json:"filters"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user