Improve login errors on missing roles (#61307)

This commit is contained in:
Pawel Kopiczko
2025-11-14 20:20:39 +00:00
committed by GitHub
parent 4bd425c037
commit ed6711ffdb
7 changed files with 36 additions and 11 deletions
+8
View File
@@ -55,6 +55,14 @@ var (
ErrPassswordlessLoginBySSOUser = &trace.AccessDeniedError{
Message: "SSO user cannot login using passwordless",
}
// ErrNonExistingRoleAssigned is issued if the user has a role that doesn't exist in
// Teleport. We have a check which prevents directly assigned roles to be deleted by auth,
// but it can be bypassed in some circumstances. E.g. roles generated by a plugin are
// deleted during plugin cleanup.
ErrNonExistingRoleAssigned = &trace.AccessDeniedError{
Message: "User is assigned one or more roles that no longer exist in Teleport. Ask your cluster admin to verify and remove direct assignments to roles which no longer exist.",
}
)
// AuthPreference defines the authentication preferences for a specific
+3
View File
@@ -343,6 +343,9 @@ func (g *Generator) postProcess(ctx context.Context, state *userloginstate.UserL
// Make sure all the roles exist. If they don't, error out.
for _, role := range state.Spec.Roles {
if _, err := g.access.GetRole(ctx, role); err != nil {
if trace.IsNotFound(err) {
return trace.Wrap(types.ErrNonExistingRoleAssigned)
}
return trace.Wrap(err)
}
}
+5
View File
@@ -69,6 +69,11 @@ const (
// auth connector's callback was encountered.
LoginFailedBadCallbackRedirectURL = "/web/msg/error/login/callback"
// LoginFailedBadCallbackMissingRoleRedirectURL is a redirect URL when an auth connector
// callback failed because it couldn't find the user's calculated role (from auth connector
// mapping) in the backend.
LoginFailedBadCallbackMissingRoleRedirectURL = "/web/msg/error/login/callback_missing_role"
// LoginFailedUnauthorizedRedirectURL is a redirect URL for when an SSO authenticates successfully,
// but the user has no matching roles in Teleport.
LoginFailedUnauthorizedRedirectURL = "/web/msg/error/login/auth"
+15 -10
View File
@@ -2312,13 +2312,13 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr
req, err := ParseSSORequestParams(r)
if err != nil {
logger.ErrorContext(r.Context(), "Failed to extract SSO parameters from request", "error", err)
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
logger.ErrorContext(r.Context(), "Failed to parse request remote address", "error", err)
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
response, err := h.cfg.ProxyClient.CreateGithubAuthRequest(r.Context(), types.GithubAuthRequest{
@@ -2331,7 +2331,7 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr
})
if err != nil {
logger.ErrorContext(r.Context(), "Error creating auth request", "error", err)
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
return response.RedirectURL
@@ -2404,10 +2404,10 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr
}
}
if errors.Is(err, auth.ErrGithubNoTeams) {
return client.LoginFailedUnauthorizedRedirectURL
return sso.LoginFailedUnauthorizedRedirectURL
}
return client.LoginFailedBadCallbackRedirectURL
return sso.LoginFailedBadCallbackRedirectURL
}
// if we created web session, set session cookie and redirect to original url
@@ -2424,7 +2424,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr
if err := SSOSetWebSessionAndRedirectURL(w, r, res, true); err != nil {
logger.ErrorContext(r.Context(), "Error setting web session.", "error", err)
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
if dwt := response.Session.GetDeviceWebToken(); dwt != nil {
@@ -2443,7 +2443,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr
logger.InfoContext(r.Context(), "Callback is redirecting to console login")
if len(response.Req.SSHPubKey)+len(response.Req.TLSPubKey) == 0 {
logger.ErrorContext(r.Context(), "Not a web or console login request")
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
redirectURL, err := ConstructSSHResponse(AuthParams{
@@ -2459,7 +2459,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr
})
if err != nil {
logger.ErrorContext(r.Context(), "Error constructing ssh response", "error", err)
return client.LoginFailedRedirectURL
return sso.LoginFailedRedirectURL
}
return redirectURL.String()
@@ -3162,6 +3162,11 @@ func (h *Handler) mfaLoginFinishSession(w http.ResponseWriter, r *http.Request,
case errors.Is(err, types.ErrPassswordlessLoginBySSOUser):
return nil, trace.Wrap(err)
// Return a friendlier error if the user has assigned a role that doesn't exist in the
// backend.
case errors.Is(err, types.ErrNonExistingRoleAssigned):
return nil, trace.Wrap(err)
// Obscure all other errors.
case err != nil:
// log the actual error.
@@ -5051,7 +5056,7 @@ func (h *Handler) WithRedirect(fn redirectHandlerFunc) httprouter.Handle {
redirectURL := fn(w, r, p)
if !IsValidRedirectURL(redirectURL) {
redirectURL = client.LoginFailedRedirectURL
redirectURL = sso.LoginFailedRedirectURL
}
http.Redirect(w, r, redirectURL, http.StatusFound)
}
@@ -5065,7 +5070,7 @@ func (h *Handler) WithMetaRedirect(fn redirectHandlerFunc) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
redirectURL := fn(w, r, p)
if !IsValidRedirectURL(redirectURL) {
redirectURL = client.LoginFailedRedirectURL
redirectURL = sso.LoginFailedRedirectURL
}
err := app.MetaRedirect(w, redirectURL)
if err != nil {
@@ -28,6 +28,9 @@ export function LoginFailed() {
<Route path={cfg.routes.loginErrorCallback}>
<LoginFailedComponent message="Unable to process SSO callback." />
</Route>
<Route path={cfg.routes.loginErrorCallbackMissingRole}>
<LoginFailedComponent message="Unable to process SSO callback. The connector has a mapping to role that does not exist. Please contact your SSO administrator." />
</Route>
<Route path={cfg.routes.loginErrorUnauthorized}>
<LoginFailedComponent message="You are not authorized, please contact your SSO administrator." />
</Route>
+1 -1
View File
@@ -39,7 +39,7 @@ import { DesktopSessionContainer as DesktopSession } from './DesktopSession';
import { HeadlessRequest } from './HeadlessRequest';
import { Login } from './Login';
import { LoginClose } from './Login/LoginClose';
import { LoginFailedComponent as LoginFailed } from './Login/LoginFailed';
import { LoginFailed } from './Login/LoginFailed';
import { LoginSuccess } from './Login/LoginSuccess';
import { LoginTerminalRedirect } from './Login/LoginTerminalRedirect';
import { Main } from './Main';
+1
View File
@@ -206,6 +206,7 @@ const cfg = {
loginErrorLegacy: '/web/msg/error/login_failed',
loginError: '/web/msg/error/login',
loginErrorCallback: '/web/msg/error/login/callback',
loginErrorCallbackMissingRole: '/web/msg/error/login/callback_missing_role',
loginErrorUnauthorized: '/web/msg/error/login/auth',
samlSloFailed: '/web/msg/error/slo',
userInvite: '/web/invite/:tokenId',