proxy: fix invalid session after logout in forward auth mode (#1062)

Currently, authorize service does handle unauthenticated request in
forward auth mode, and return status 401.

But proxy has not handled the response yet, and always returns 403 for
both unauthenticated and unauthorized request. That breaks session
handling in forward auth mode. That said, if user was signed out, or for
any reason, authorize service return 401 status, proxy does not redirect
user to re-signin, but always return 403.

To fix it, proxy is changed to handle envoy check response in more
details, to distinguish between 401 and 403 status.

Thanks to @simbaja for rasing the problem and come up with original fix.

Fixes #1014
Fixes #858
This commit is contained in:
Cuong Manh Le 2020-07-14 01:07:49 +07:00 committed by GitHub
parent 7437a4967d
commit 58fb6ea3c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 34 deletions

View file

@ -101,38 +101,31 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
return httputil.NewError(http.StatusForbidden, errors.New(http.StatusText(http.StatusForbidden)))
}
// the route to validate will be pulled from the uri queryparam
// or inferred from forwarding headers
uriString := r.FormValue("uri")
if uriString == "" {
if r.Header.Get(httputil.HeaderForwardedProto) == "" || r.Header.Get(httputil.HeaderForwardedHost) == "" {
return httputil.NewError(http.StatusBadRequest, errors.New("no uri to validate"))
}
uriString = r.Header.Get(httputil.HeaderForwardedProto) + "://" +
r.Header.Get(httputil.HeaderForwardedHost) +
r.Header.Get(httputil.HeaderForwardedURI)
}
uri, err := urlutil.ParseAndValidateURL(uriString)
uri, err := getURIStringFromRequest(r)
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
authorized, err := p.isAuthorized(w, r)
ar, err := p.isAuthorized(w, r)
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
if authorized {
if ar.authorized {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Access to %s is allowed.", uri.Host)
return nil
}
unAuthenticated := ar.statusCode == http.StatusUnauthorized
if unAuthenticated {
p.sessionStore.ClearSession(w, r)
}
_, err = sessions.FromContext(r.Context())
hasSession := err == nil
if hasSession {
if hasSession && !unAuthenticated {
return httputil.NewError(http.StatusForbidden, errors.New("access denied"))
}
@ -140,19 +133,46 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
return httputil.NewError(http.StatusUnauthorized, err)
}
// Traefik set the uri in the header, we must add it to redirect uri if present. Otherwise, request like
// https://example.com/foo will be redirected to https://example.com after authentication.
if xfu := r.Header.Get(httputil.HeaderForwardedURI); xfu != "" {
uri.Path += xfu
}
// redirect to authenticate
authN := *p.authenticateSigninURL
q := authN.Query()
q.Set(urlutil.QueryCallbackURI, uri.String())
q.Set(urlutil.QueryRedirectURI, uri.String()) // final destination
q.Set(urlutil.QueryForwardAuth, urlutil.StripPort(r.Host)) // add fwd auth to trusted audience
authN.RawQuery = q.Encode()
httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &authN).String(), http.StatusFound)
p.forwardAuthRedirectToSignInWithURI(w, r, uri)
return nil
})
}
// forwardAuthRedirectToSignInWithURI redirects request to authenticate signin url,
// with all necessary information extracted from given input uri.
func (p *Proxy) forwardAuthRedirectToSignInWithURI(w http.ResponseWriter, r *http.Request, uri *url.URL) {
// Traefik set the uri in the header, we must add it to redirect uri if present. Otherwise, request like
// https://example.com/foo will be redirected to https://example.com after authentication.
if xfu := r.Header.Get(httputil.HeaderForwardedURI); xfu != "" {
uri.Path += xfu
}
// redirect to authenticate
authN := *p.authenticateSigninURL
q := authN.Query()
q.Set(urlutil.QueryCallbackURI, uri.String())
q.Set(urlutil.QueryRedirectURI, uri.String()) // final destination
q.Set(urlutil.QueryForwardAuth, urlutil.StripPort(r.Host)) // add fwd auth to trusted audience
authN.RawQuery = q.Encode()
httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &authN).String(), http.StatusFound)
}
func getURIStringFromRequest(r *http.Request) (*url.URL, error) {
// the route to validate will be pulled from the uri queryparam
// or inferred from forwarding headers
uriString := r.FormValue("uri")
if uriString == "" {
if r.Header.Get(httputil.HeaderForwardedProto) == "" || r.Header.Get(httputil.HeaderForwardedHost) == "" {
return nil, errors.New("no uri to validate")
}
uriString = r.Header.Get(httputil.HeaderForwardedProto) + "://" +
r.Header.Get(httputil.HeaderForwardedHost) +
r.Header.Get(httputil.HeaderForwardedURI)
}
uri, err := urlutil.ParseAndValidateURL(uriString)
if err != nil {
return nil, err
}
return uri, nil
}