Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2839,6 +2839,10 @@ func (a *ServerWithRoles) DeleteUser(ctx context.Context, user string) error {
return trace.Wrap(err)
}

if err := checkOktaAccessByName(&a.context, a.authServer, user, types.VerbDelete); err != nil {
return trace.Wrap(err)
}

return a.authServer.DeleteUser(ctx, user)
}

Expand Down Expand Up @@ -3421,6 +3425,11 @@ func (a *ServerWithRoles) CreateUser(ctx context.Context, user types.User) error
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbCreate); err != nil {
return trace.Wrap(err)
}

if err := checkOktaOrigin(&a.context, user); err != nil {
return trace.Wrap(err)
}

return a.authServer.CreateUser(ctx, user)
}

Expand All @@ -3431,6 +3440,14 @@ func (a *ServerWithRoles) UpdateUser(ctx context.Context, user types.User) error
return trace.Wrap(err)
}

if err := checkOktaOrigin(&a.context, user); err != nil {
return trace.Wrap(err)
}

if err := checkOktaAccessByName(&a.context, a.authServer, user.GetName(), types.VerbUpdate); err != nil {
return trace.Wrap(err)
}

return a.authServer.UpdateUser(ctx, user)
}

Expand All @@ -3439,6 +3456,14 @@ func (a *ServerWithRoles) UpsertUser(u types.User) error {
return trace.Wrap(err)
}

if err := checkOktaOrigin(&a.context, u); err != nil {
return trace.Wrap(err)
}

if err := checkOktaAccessByName(&a.context, a.authServer, u.GetName(), types.VerbUpdate); err != nil {
return trace.Wrap(err)
}

createdBy := u.GetCreatedBy()
if createdBy.IsEmpty() {
u.SetCreatedBy(types.CreatedBy{
Expand All @@ -3464,6 +3489,19 @@ func (a *ServerWithRoles) CompareAndSwapUser(ctx context.Context, new, existing
return trace.Wrap(err)
}

if err := checkOktaOrigin(&a.context, new); err != nil {
return trace.Wrap(err)
}

// Checking the `existing` origin should be enough to assert that okta has
// write access to the user, because if the backend record says something
// different then the `CompareAndSwap()` will fail anyway, and this way we
// save ourselves a backend user lookup.

if err := checkOktaAccess(&a.context, existing, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}

return a.authServer.CompareAndSwapUser(ctx, new, existing)
}

Expand Down Expand Up @@ -6946,3 +6984,70 @@ func verbsToReplaceResourceWithOrigin(stored types.ResourceWithOrigin) []string
}
return verbs
}

// checkOktaOrigin checks that the supplied user has an appropriate origin label
// set. In this case "appropriate" means having the Okta origin set if and only
// if the supplied auth context has the build-in Okta role. An auth context
// without the Okta role may supply any origin value *other than* okta
// (including nil).
// Returns an error if the user origin value is "inappropriate".
func checkOktaOrigin(authzCtx *authz.Context, user types.User) error {
isOktaService := authz.HasBuiltinRole(*authzCtx, string(types.RoleOkta))
hasOktaOrigin := user.Origin() == types.OriginOkta

switch {
case isOktaService && !hasOktaOrigin:
return trace.BadParameter(`Okta service must supply "okta" origin`)

case !isOktaService && hasOktaOrigin:
return trace.BadParameter(`Must be Okta service to set "okta" origin`)

default:
return nil
}
}

// checkOktaAccessByName gates access to update operations on user records
// based on the origin label on the supplied user record.
func checkOktaAccessByName(authzCtx *authz.Context, users services.UsersService, existingUsername string, verb string) error {
existingUser, err := users.GetUser(existingUsername, false)
if err != nil && !trace.IsNotFound(err) {
return trace.Wrap(err)
}

return checkOktaAccess(authzCtx, existingUser, verb)
}

// checkOktaAccess gates access to update operations on user records based
// on the origin label on the supplied user record.
//
// A nil `existingUser` is interpreted as there being no matching existing
// user in the cluster; if there is no user then there is no user to
// overwrite, so access is granted
func checkOktaAccess(authzCtx *authz.Context, existingUser types.User, verb string) error {
// We base or decision to allow write access to a resource on the Origin
// label. If there is no existing user, then there can be no label to block
// access, so anyone can do anything.
if existingUser == nil {
return nil
}

if !authz.HasBuiltinRole(*authzCtx, string(types.RoleOkta)) {
// The only thing a non-okta service caller is allowed to do to an
// Okta-origin user is delete it
if (existingUser.Origin() == types.OriginOkta) && (verb != types.VerbDelete) {
return trace.BadParameter("Okta origin may not be changed")
}
return nil
}

// An okta-service caller only has rights over the user if they have an
// "Origin: Okta" label
if existingUser.Origin() == types.OriginOkta {
return nil
}

// If we get to here, we have exhausted all possible ways that the caller
// may be allowed to modify a user, so they get AccessDenied by default.
return trace.AccessDenied("Okta service may only %s Okta users", verb)
}
Loading