Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Bots to submit access request reviews #33375

Merged
merged 16 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4360,7 +4360,11 @@ func (a *Server) SetAccessRequestState(ctx context.Context, params types.AccessR
return trace.Wrap(err)
}

func (a *Server) SubmitAccessReview(ctx context.Context, params types.AccessReviewSubmission) (types.AccessRequest, error) {
func (a *Server) SubmitAccessReview(
ctx context.Context,
params types.AccessReviewSubmission,
identity tlsca.Identity,
) (types.AccessRequest, error) {
// When promoting a request, the access list name must be set.
if params.Review.ProposedState.IsPromoted() && params.Review.GetAccessListName() == "" {
return nil, trace.BadParameter("promoted access list can be only set when promoting access requests")
Expand All @@ -4372,7 +4376,7 @@ func (a *Server) SubmitAccessReview(ctx context.Context, params types.AccessRevi
}

// set up a checker for the review author
checker, err := services.NewReviewPermissionChecker(ctx, a, params.Review.Author)
checker, err := services.NewReviewPermissionChecker(ctx, a, params.Review.Author, identity)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
10 changes: 7 additions & 3 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2450,8 +2450,12 @@ func (a *ServerWithRoles) GetAccessRequests(ctx context.Context, filter types.Ac
// their own requests. we therefore subselect the filter results to show only those requests
// that the user *is* allowed to see (specifically, their own requests + requests that they
// are allowed to review).

checker, err := services.NewReviewPermissionChecker(ctx, a.authServer, a.context.User.GetName())
checker, err := services.NewReviewPermissionChecker(
ctx,
a.authServer,
a.context.User.GetName(),
a.context.Identity.GetIdentity(),
)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -2561,7 +2565,7 @@ func (a *ServerWithRoles) SubmitAccessReview(ctx context.Context, submission typ
// under optimistic locking at the level of the backend service. the correctness of the
// author field is all that needs to be enforced at this level.

return a.authServer.SubmitAccessReview(ctx, submission)
return a.authServer.SubmitAccessReview(ctx, submission, a.context.Identity.GetIdentity())
}

func (a *ServerWithRoles) GetAccessCapabilities(ctx context.Context, req types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) {
Expand Down
53 changes: 51 additions & 2 deletions lib/services/access_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,12 +902,61 @@ Outer:
return len(needsAllow) == 0, nil
}

func NewReviewPermissionChecker(ctx context.Context, getter RequestValidatorGetter, username string) (ReviewPermissionChecker, error) {
type userStateRoleOverride struct {
strideynet marked this conversation as resolved.
Show resolved Hide resolved
UserState
Roles []string
}

func (u userStateRoleOverride) GetRoles() []string {
return u.Roles
}

func NewReviewPermissionChecker(
ctx context.Context,
getter RequestValidatorGetter,
username string,
identity tlsca.Identity,
) (ReviewPermissionChecker, error) {
uls, err := GetUserOrLoginState(ctx, getter, username)
if err != nil {
return ReviewPermissionChecker{}, trace.Wrap(err)
}

// By default, the users freshly fetched roles are used rather than the
// roles on the x509 identity. This prevents recursive access request
// review.
//
// For bots, however, the roles on the identity must be used. This is
// because the certs output by a bot always use role impersonation and the
// role directly assigned to a bot has minimal permissions.
if uls.IsBot() {
if identity.Username != username {
// It should not be possible for these to be different as a
// guard in AuthorizeAccessReviewRequest prevents submitting a
// request as another user unless you have the admin role. This
// safeguard protects against that regressing and creating an
// inconsistent state.
return ReviewPermissionChecker{}, trace.BadParameter(
"bot identity username and review author mismatch",
)
}
if len(identity.ActiveRequests) > 0 {
// It should not be possible for a bot's output certificates to
// have active requests - but this additional check safeguards us
// against a regression elsewhere and prevents recursive access
// requests occurring.
return ReviewPermissionChecker{}, trace.BadParameter(
"bot should not have active requests",
)
}

// Override list of roles to roles currently present on the x509 ident.
uls = userStateRoleOverride{
UserState: uls,
Roles: identity.Groups,
}
}

c := ReviewPermissionChecker{
UserState: uls,
}
Expand All @@ -917,7 +966,7 @@ func NewReviewPermissionChecker(ctx context.Context, getter RequestValidatorGett

// load all statically assigned roles for the user and
// use them to build our checker state.
for _, roleName := range c.UserState.GetRoles() {
for _, roleName := range uls.GetRoles() {
role, err := getter.GetRole(ctx, roleName)
if err != nil {
return ReviewPermissionChecker{}, trace.Wrap(err)
Expand Down