-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Export SSO types and functions in lib/web #17530
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
Changes from all commits
f997a78
a684f96
3c91341
57c8015
a797c3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,9 +77,9 @@ import ( | |
| ) | ||
|
|
||
| const ( | ||
| // ssoLoginConsoleErr is a generic error message to hide revealing sso login failure msgs. | ||
| ssoLoginConsoleErr = "Failed to login. Please check Teleport's log for more details." | ||
| metaRedirectHTML = ` | ||
| // SSOLoginFailureMessage is a generic error message to avoid disclosing sensitive SSO failure messages. | ||
| SSOLoginFailureMessage = "Failed to login. Please check Teleport's log for more details." | ||
| metaRedirectHTML = ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
|
|
@@ -184,10 +184,10 @@ type Config struct { | |
| // Enables web UI if set. | ||
| StaticFS http.FileSystem | ||
|
|
||
| // cachedSessionLingeringThreshold specifies the time the session will linger | ||
| // CachedSessionLingeringThreshold specifies the time the session will linger | ||
| // in the cache before getting purged after it has expired. | ||
| // Defaults to cachedSessionLingeringThreshold if unspecified. | ||
| cachedSessionLingeringThreshold *time.Duration | ||
| CachedSessionLingeringThreshold *time.Duration | ||
|
|
||
| // ClusterFeatures contains flags for supported/unsupported features. | ||
| ClusterFeatures proto.Features | ||
|
|
@@ -266,8 +266,8 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { | |
| } | ||
|
|
||
| sessionLingeringThreshold := cachedSessionLingeringThreshold | ||
| if cfg.cachedSessionLingeringThreshold != nil { | ||
| sessionLingeringThreshold = *cfg.cachedSessionLingeringThreshold | ||
| if cfg.CachedSessionLingeringThreshold != nil { | ||
| sessionLingeringThreshold = *cfg.CachedSessionLingeringThreshold | ||
| } | ||
|
|
||
| auth, err := newSessionCache(sessionCacheOptions{ | ||
|
|
@@ -1211,17 +1211,17 @@ func (h *Handler) oidcLoginWeb(w http.ResponseWriter, r *http.Request, p httprou | |
| logger := h.log.WithField("auth", "oidc") | ||
| logger.Debug("Web login start.") | ||
|
|
||
| req, err := parseSSORequestParams(r) | ||
| req, err := ParseSSORequestParams(r) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to extract SSO parameters from request.") | ||
| return client.LoginFailedRedirectURL | ||
| } | ||
|
|
||
| response, err := h.cfg.ProxyClient.CreateOIDCAuthRequest(r.Context(), types.OIDCAuthRequest{ | ||
| CSRFToken: req.csrfToken, | ||
| ConnectorID: req.connectorID, | ||
| CSRFToken: req.CSRFToken, | ||
| ConnectorID: req.ConnectorID, | ||
| CreateWebSession: true, | ||
| ClientRedirectURL: req.clientRedirectURL, | ||
| ClientRedirectURL: req.ClientRedirectURL, | ||
| CheckUser: true, | ||
| ProxyAddress: r.Host, | ||
| }) | ||
|
|
@@ -1237,17 +1237,17 @@ func (h *Handler) githubLoginWeb(w http.ResponseWriter, r *http.Request, p httpr | |
| logger := h.log.WithField("auth", "github") | ||
| logger.Debug("Web login start.") | ||
|
|
||
| req, err := parseSSORequestParams(r) | ||
| req, err := ParseSSORequestParams(r) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to extract SSO parameters from request.") | ||
| return client.LoginFailedRedirectURL | ||
| } | ||
|
|
||
| response, err := h.cfg.ProxyClient.CreateGithubAuthRequest(r.Context(), types.GithubAuthRequest{ | ||
| CSRFToken: req.csrfToken, | ||
| ConnectorID: req.connectorID, | ||
| CSRFToken: req.CSRFToken, | ||
| ConnectorID: req.ConnectorID, | ||
| CreateWebSession: true, | ||
| ClientRedirectURL: req.clientRedirectURL, | ||
| ClientRedirectURL: req.ClientRedirectURL, | ||
| }) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Error creating auth request.") | ||
|
|
@@ -1265,12 +1265,12 @@ func (h *Handler) githubLoginConsole(w http.ResponseWriter, r *http.Request, p h | |
| req := new(client.SSOLoginConsoleReq) | ||
| if err := httplib.ReadJSON(r, req); err != nil { | ||
| logger.WithError(err).Error("Error reading json.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| if err := req.CheckAndSetDefaults(); err != nil { | ||
| logger.WithError(err).Error("Missing request parameters.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| response, err := h.cfg.ProxyClient.CreateGithubAuthRequest(r.Context(), types.GithubAuthRequest{ | ||
|
|
@@ -1285,7 +1285,7 @@ func (h *Handler) githubLoginConsole(w http.ResponseWriter, r *http.Request, p h | |
| }) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to create Github auth request.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| return &client.SSOLoginConsoleResponse{ | ||
|
|
@@ -1307,7 +1307,7 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr | |
| // this improves the UX by terminating the failed SSO flow immediately, rather than hoping for a timeout. | ||
| if requestID := r.URL.Query().Get("state"); requestID != "" { | ||
| if request, errGet := h.cfg.ProxyClient.GetGithubAuthRequest(r.Context(), requestID); errGet == nil && !request.CreateWebSession { | ||
| if redURL, errEnc := redirectURLWithError(request.ClientRedirectURL, err); errEnc == nil { | ||
| if redURL, errEnc := RedirectURLWithError(request.ClientRedirectURL, err); errEnc == nil { | ||
| return redURL.String() | ||
| } | ||
| } | ||
|
|
@@ -1323,19 +1323,19 @@ func (h *Handler) githubCallback(w http.ResponseWriter, r *http.Request, p httpr | |
| if response.Req.CreateWebSession { | ||
| logger.Infof("Redirecting to web browser.") | ||
|
|
||
| res := &ssoCallbackResponse{ | ||
| csrfToken: response.Req.CSRFToken, | ||
| username: response.Username, | ||
| sessionName: response.Session.GetName(), | ||
| clientRedirectURL: response.Req.ClientRedirectURL, | ||
| res := &SSOCallbackResponse{ | ||
| CSRFToken: response.Req.CSRFToken, | ||
| Username: response.Username, | ||
| SessionName: response.Session.GetName(), | ||
| ClientRedirectURL: response.Req.ClientRedirectURL, | ||
| } | ||
|
|
||
| if err := ssoSetWebSessionAndRedirectURL(w, r, res, true); err != nil { | ||
| if err := SSOSetWebSessionAndRedirectURL(w, r, res, true); err != nil { | ||
| logger.WithError(err).Error("Error setting web session.") | ||
| return client.LoginFailedRedirectURL | ||
| } | ||
|
|
||
| return res.clientRedirectURL | ||
| return res.ClientRedirectURL | ||
| } | ||
|
|
||
| logger.Infof("Callback is redirecting to console login.") | ||
|
|
@@ -1369,12 +1369,12 @@ func (h *Handler) oidcLoginConsole(w http.ResponseWriter, r *http.Request, p htt | |
| req := new(client.SSOLoginConsoleReq) | ||
| if err := httplib.ReadJSON(r, req); err != nil { | ||
| logger.WithError(err).Error("Error reading json.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| if err := req.CheckAndSetDefaults(); err != nil { | ||
| logger.WithError(err).Error("Missing request parameters.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| response, err := h.cfg.ProxyClient.CreateOIDCAuthRequest(r.Context(), types.OIDCAuthRequest{ | ||
|
|
@@ -1391,7 +1391,7 @@ func (h *Handler) oidcLoginConsole(w http.ResponseWriter, r *http.Request, p htt | |
| }) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to create OIDC auth request.") | ||
| return nil, trace.AccessDenied(ssoLoginConsoleErr) | ||
| return nil, trace.AccessDenied(SSOLoginFailureMessage) | ||
| } | ||
|
|
||
| return &client.SSOLoginConsoleResponse{ | ||
|
|
@@ -1413,7 +1413,7 @@ func (h *Handler) oidcCallback(w http.ResponseWriter, r *http.Request, p httprou | |
| // this improves the UX by terminating the failed SSO flow immediately, rather than hoping for a timeout. | ||
| if requestID := r.URL.Query().Get("state"); requestID != "" { | ||
| if request, errGet := h.cfg.ProxyClient.GetOIDCAuthRequest(r.Context(), requestID); errGet == nil && !request.CreateWebSession { | ||
| if redURL, errEnc := redirectURLWithError(request.ClientRedirectURL, err); errEnc == nil { | ||
| if redURL, errEnc := RedirectURLWithError(request.ClientRedirectURL, err); errEnc == nil { | ||
| return redURL.String() | ||
| } | ||
| } | ||
|
|
@@ -1430,19 +1430,19 @@ func (h *Handler) oidcCallback(w http.ResponseWriter, r *http.Request, p httprou | |
| if response.Req.CreateWebSession { | ||
| logger.Info("Redirecting to web browser.") | ||
|
|
||
| res := &ssoCallbackResponse{ | ||
| csrfToken: response.Req.CSRFToken, | ||
| username: response.Username, | ||
| sessionName: response.Session.GetName(), | ||
| clientRedirectURL: response.Req.ClientRedirectURL, | ||
| res := &SSOCallbackResponse{ | ||
| CSRFToken: response.Req.CSRFToken, | ||
| Username: response.Username, | ||
| SessionName: response.Session.GetName(), | ||
| ClientRedirectURL: response.Req.ClientRedirectURL, | ||
| } | ||
|
|
||
| if err := ssoSetWebSessionAndRedirectURL(w, r, res, true); err != nil { | ||
| if err := SSOSetWebSessionAndRedirectURL(w, r, res, true); err != nil { | ||
| logger.WithError(err).Error("Error setting web session.") | ||
| return client.LoginFailedRedirectURL | ||
| } | ||
|
|
||
| return res.clientRedirectURL | ||
| return res.ClientRedirectURL | ||
| } | ||
|
|
||
| logger.Info("Callback redirecting to console login.") | ||
|
|
@@ -1593,7 +1593,11 @@ func ConstructSSHResponse(response AuthParams) (*url.URL, error) { | |
| return u, nil | ||
| } | ||
|
|
||
| func redirectURLWithError(clientRedirectURL string, errReply error) (*url.URL, error) { | ||
| // RedirectURLWithError adds an err query parameter to the given redirect URL with the | ||
| // given errReply message and returns the new URL. If the given URL cannot be parsed, | ||
| // an error is returned with a nil URL. It is used to return an error back to the | ||
| // original URL in an SSO callback when validation fails. | ||
| func RedirectURLWithError(clientRedirectURL string, errReply error) (*url.URL, error) { | ||
| u, err := url.Parse(clientRedirectURL) | ||
| if err != nil { | ||
| return nil, trace.Wrap(err) | ||
|
|
@@ -3167,13 +3171,23 @@ func makeTeleportClientConfig(ctx context.Context, sesCtx *SessionContext) (*cli | |
| return config, nil | ||
| } | ||
|
|
||
| type ssoRequestParams struct { | ||
| clientRedirectURL string | ||
| connectorID string | ||
| csrfToken string | ||
| // SSORequestParams holds parameters parsed out of a HTTP request initiating an | ||
| // SSO login. See ParseSSORequestParams(). | ||
| type SSORequestParams struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing doc for type and fields.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| // ClientRedirectURL is the URL specified in the query parameter | ||
| // redirect_url, which will be unescaped here. | ||
| ClientRedirectURL string | ||
| // ConnectorID identifies the SSO connector to use to log in, from | ||
| // the connector_id query parameter. | ||
| ConnectorID string | ||
| // CSRFToken is the token in the CSRF cookie header. | ||
| CSRFToken string | ||
| } | ||
|
|
||
| func parseSSORequestParams(r *http.Request) (*ssoRequestParams, error) { | ||
| // ParseSSORequestParams extracts the SSO request parameters from an http.Request, | ||
| // returning them in an SSORequestParams struct. If any fields are not present, | ||
| // an error is returned. | ||
| func ParseSSORequestParams(r *http.Request) (*SSORequestParams, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing doc.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| // Manually grab the value from query param "redirect_url". | ||
| // | ||
| // The "redirect_url" param can contain its own query params such as in | ||
|
|
@@ -3205,37 +3219,52 @@ func parseSSORequestParams(r *http.Request) (*ssoRequestParams, error) { | |
| return nil, trace.Wrap(err) | ||
| } | ||
|
|
||
| return &ssoRequestParams{ | ||
| clientRedirectURL: clientRedirectURL, | ||
| connectorID: connectorID, | ||
| csrfToken: csrfToken, | ||
| return &SSORequestParams{ | ||
| ClientRedirectURL: clientRedirectURL, | ||
| ConnectorID: connectorID, | ||
| CSRFToken: csrfToken, | ||
| }, nil | ||
| } | ||
|
|
||
| type ssoCallbackResponse struct { | ||
| csrfToken string | ||
| username string | ||
| sessionName string | ||
| clientRedirectURL string | ||
| // SSOCallbackResponse holds the parameters for validating and executing an SSO | ||
| // callback URL. See SSOSetWebSessionAndRedirectURL(). | ||
| type SSOCallbackResponse struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing doc for type and fields.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| // CSRFToken is the token provided in the originating SSO login request | ||
| // to be validated against. | ||
| CSRFToken string | ||
| // Username is the authenticated teleport username of the user that has | ||
| // logged in, provided by the SSO provider. | ||
| Username string | ||
| // SessionName is the name of the session generated by auth server if | ||
| // requested in the SSO request. | ||
| SessionName string | ||
| // ClientRedirectURL is the URL to redirect back to on completion of | ||
| // the SSO login process. | ||
| ClientRedirectURL string | ||
| } | ||
|
|
||
| func ssoSetWebSessionAndRedirectURL(w http.ResponseWriter, r *http.Request, response *ssoCallbackResponse, verifyCSRF bool) error { | ||
| // SSOSetWebSessionAndRedirectURL validates the CSRF token in the response | ||
| // against that in the request, validates that the callback URL in the response | ||
| // can be parsed, and sets a session cookie with the username and session name | ||
| // from the response. On success, nil is returned. If the validation fails, an | ||
| // error is returned. | ||
| func SSOSetWebSessionAndRedirectURL(w http.ResponseWriter, r *http.Request, response *SSOCallbackResponse, verifyCSRF bool) error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing doc.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| if verifyCSRF { | ||
| if err := csrf.VerifyToken(response.csrfToken, r); err != nil { | ||
| if err := csrf.VerifyToken(response.CSRFToken, r); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
| } | ||
|
|
||
| if err := SetSessionCookie(w, response.username, response.sessionName); err != nil { | ||
| if err := SetSessionCookie(w, response.Username, response.SessionName); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
|
|
||
| parsedURL, err := url.Parse(response.clientRedirectURL) | ||
| parsedURL, err := url.Parse(response.ClientRedirectURL) | ||
| if err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
|
|
||
| response.clientRedirectURL = parsedURL.RequestURI() | ||
| response.ClientRedirectURL = parsedURL.RequestURI() | ||
|
|
||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing doc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done