From 9d3b907bb8001df9c99e15be1317144bbe2b5825 Mon Sep 17 00:00:00 2001 From: Trent Clarke Date: Fri, 13 Dec 2024 21:44:14 +1100 Subject: [PATCH] [v17] Automatially add IC Account to Account Assignments Access Requests (#50190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Automatially add IC Account to Account Assignments Access Requests The UI needs access to the account associated with an Account Assignment in order to display the enclosing Account, otherwise the user will not be able to see their assogned permission sets. This patch automatically adds the enclosing account for any account assignments in a resource access request and allows the user to see ther human-friendly names in the access request listing. * Apply suggestions from code review Co-authored-by: Sakshyam Shah Co-authored-by: Marek Smoliński * Code review sugestions --------- Co-authored-by: Sakshyam Shah Co-authored-by: Marek Smoliński --- api/accessrequest/access_request.go | 2 +- api/types/constants.go | 2 + lib/auth/auth.go | 109 +++++++++++++++++----------- 3 files changed, 71 insertions(+), 42 deletions(-) diff --git a/api/accessrequest/access_request.go b/api/accessrequest/access_request.go index 1af10ac4400d3..7b82cfc8f25a6 100644 --- a/api/accessrequest/access_request.go +++ b/api/accessrequest/access_request.go @@ -58,7 +58,7 @@ func GetResourceDetails(ctx context.Context, clusterName string, lister client.L var resourceIDs []types.ResourceID for _, resourceID := range ids { // We're interested in hostname or friendly name details. These apply to - // nodes, app servers, and user groups. + // nodes, app servers, user groups and Identity Center resources. switch resourceID.Kind { case types.KindNode, types.KindApp, types.KindUserGroup, types.KindIdentityCenterAccount: resourceIDs = append(resourceIDs, resourceID) diff --git a/api/types/constants.go b/api/types/constants.go index 369cc46730bf0..61d830633e80c 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -1296,6 +1296,8 @@ var RequestableResourceKinds = []string{ KindKubeCertificateSigningRequest, KindKubeIngress, KindSAMLIdPServiceProvider, + KindIdentityCenterAccount, + KindIdentityCenterAccountAssignment, } // The list below needs to be kept in sync with `kubernetesResourceKindOptions` diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 2f4efa8c6843e..8d721850fb98a 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -5099,47 +5099,11 @@ func (a *Server) CreateAccessRequestV2(ctx context.Context, req types.AccessRequ } // Look for user groups and associated applications to the request. - requestedResourceIDs := req.GetRequestedResourceIDs() - var additionalResources []types.ResourceID - - var userGroups []types.ResourceID - existingApps := map[string]struct{}{} - for _, resource := range requestedResourceIDs { - switch resource.Kind { - case types.KindApp: - existingApps[resource.Name] = struct{}{} - case types.KindUserGroup: - userGroups = append(userGroups, resource) - } - } - - for _, resource := range userGroups { - if resource.Kind != types.KindUserGroup { - continue - } - - userGroup, err := a.GetUserGroup(ctx, resource.Name) - if err != nil { - return nil, trace.Wrap(err) - } - - for _, app := range userGroup.GetApplications() { - // Only add to the request if we haven't already added it. - if _, ok := existingApps[app]; !ok { - additionalResources = append(additionalResources, types.ResourceID{ - ClusterName: resource.ClusterName, - Kind: types.KindApp, - Name: app, - }) - existingApps[app] = struct{}{} - } - } - } - - if len(additionalResources) > 0 { - requestedResourceIDs = append(requestedResourceIDs, additionalResources...) - req.SetRequestedResourceIDs(requestedResourceIDs) + requestedResourceIDs, err := a.appendImplicitlyRequiredResources(ctx, req.GetRequestedResourceIDs()) + if err != nil { + return nil, trace.Wrap(err, "adding additional implicitly required resources") } + req.SetRequestedResourceIDs(requestedResourceIDs) if req.GetDryRun() { _, promotions := a.generateAccessRequestPromotions(ctx, req) @@ -5169,7 +5133,7 @@ func (a *Server) CreateAccessRequestV2(ctx context.Context, req types.AccessRequ } } - err := a.emitter.EmitAuditEvent(a.closeCtx, &apievents.AccessRequestCreate{ + err = a.emitter.EmitAuditEvent(a.closeCtx, &apievents.AccessRequestCreate{ Metadata: apievents.Metadata{ Type: events.AccessRequestCreateEvent, Code: events.AccessRequestCreateCode, @@ -5251,6 +5215,69 @@ func (a *Server) CreateAccessRequestV2(ctx context.Context, req types.AccessRequ return req, nil } +// appendImplicitlyRequiredResources examines the set of requested resources and adds +// any extra resources that are implicitly required by the request. +func (a *Server) appendImplicitlyRequiredResources(ctx context.Context, resources []types.ResourceID) ([]types.ResourceID, error) { + addedApps := utils.NewSet[string]() + var userGroups []types.ResourceID + var accountAssignments []types.ResourceID + + for _, resource := range resources { + switch resource.Kind { + case types.KindApp: + addedApps.Add(resource.Name) + case types.KindUserGroup: + userGroups = append(userGroups, resource) + case types.KindIdentityCenterAccountAssignment: + accountAssignments = append(accountAssignments, resource) + } + } + + for _, resource := range userGroups { + userGroup, err := a.GetUserGroup(ctx, resource.Name) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, app := range userGroup.GetApplications() { + // Only add to the request if we haven't already added it. + if !addedApps.Contains(app) { + resources = append(resources, types.ResourceID{ + ClusterName: resource.ClusterName, + Kind: types.KindApp, + Name: app, + }) + addedApps.Add(app) + } + } + } + + icAccounts := utils.NewSet[string]() + for _, resource := range accountAssignments { + // The UI needs access to the account associated with an Account Assignment + // in order to display the enclosing Account, otherwise the user will not + // be able to see their assigned permission sets. + assignmentID := services.IdentityCenterAccountAssignmentID(resource.Name) + asmt, err := a.Services.IdentityCenter.GetAccountAssignment(ctx, assignmentID) + if err != nil { + return nil, trace.Wrap(err, "fetching identity center account assignment") + } + + if icAccounts.Contains(asmt.GetSpec().GetAccountId()) { + continue + } + + resources = append(resources, types.ResourceID{ + ClusterName: resource.ClusterName, + Kind: types.KindIdentityCenterAccount, + Name: asmt.GetSpec().GetAccountId(), + }) + icAccounts.Add(asmt.GetSpec().GetAccountId()) + } + + return resources, nil +} + // generateAccessRequestPromotions will return potential access list promotions for an access request. On error, this function will log // the error and return whatever it has. The caller is expected to deal with the possibility of a nil promotions object. func (a *Server) generateAccessRequestPromotions(ctx context.Context, req types.AccessRequest) (types.AccessRequest, *types.AccessRequestAllowedPromotions) {