From bf509516ab57fc27ffcf4cfc16469c2f46271f4c Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Mon, 26 Aug 2019 17:20:18 +0200 Subject: [PATCH 1/4] Support single-host mode of Che server. * A custom URI prefix for the auth redirect can be configured. This is so that we can construct valid externally reachable URLs even behind a path-rewriting ingress * Change the order in which the auth token is located. First we try to find it in the query params, then in the Authorization header as a bearer token and only then in the cookie. This enables us to "refresh" the token from the client side easily. * On any error to validate the token (apart from the inability to parse the token in the first place) we know send the auth redirect instead of an error. This should help the client side refresh the token on timeouts, etc. * Do not set the cookie in the response if cookies are not enabled in the config. * Respond with 403 - Forbidden if cookies are not enabled. In this case the client needs to directly authenticate with the backend server. --- config/config.go | 24 ++++++++------- jwt/jwt.go | 68 ++++++++++++++++++++++++------------------- jwt/jwt_test.go | 2 +- jwt/proxy_handlers.go | 14 +++++++-- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/config/config.go b/config/config.go index 7bb4278..d3ec3ba 100644 --- a/config/config.go +++ b/config/config.go @@ -20,7 +20,7 @@ import ( "os" "time" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) // URL is a custom URL type that allows validation at configuration load time. @@ -113,17 +113,19 @@ type SignerProxyConfig struct { } type VerifierConfig struct { - Upstream URL `yaml:"upstream"` + Upstream URL `yaml:"upstream"` // Changed to string to be more JWT spec compliant - it can be either string or URL - Audience string `yaml:"audience"` - CookiesEnabled bool `yaml:"auth_cookies_enabled"` - AuthRedirect string `yaml:"auth_redirect_url"` - MaxSkew time.Duration `yaml:"max_skew"` - MaxTTL time.Duration `yaml:"max_ttl"` - KeyServer RegistrableComponentConfig `yaml:"key_server"` - NonceStorage RegistrableComponentConfig `yaml:"nonce_storage"` - Excludes []string `yaml:"excludes"` - ClaimsVerifiers []RegistrableComponentConfig `yaml:"claims_verifiers"` + Audience string `yaml:"audience"` + CookiesEnabled bool `yaml:"auth_cookies_enabled"` + CookiePath string `yaml:"cookie_path"` + AuthRedirect string `yaml:"auth_redirect_url"` + MaxSkew time.Duration `yaml:"max_skew"` + MaxTTL time.Duration `yaml:"max_ttl"` + KeyServer RegistrableComponentConfig `yaml:"key_server"` + NonceStorage RegistrableComponentConfig `yaml:"nonce_storage"` + Excludes []string `yaml:"excludes"` + ClaimsVerifiers []RegistrableComponentConfig `yaml:"claims_verifiers"` + AuthErrorRedirectURIPrefix string `yaml:"auth_error_redirect_uri_prefix"` } type SignerParams struct { diff --git a/jwt/jwt.go b/jwt/jwt.go index 2752955..b287b78 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -68,37 +68,35 @@ func Sign(req *http.Request, key *key.PrivateKey, params config.SignerParams) er return nil } -func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncestorage.NonceStorage, cookiesEnabled bool, expectedAudience string, maxSkew time.Duration, maxTTL time.Duration) (jose.Claims, error) { +func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncestorage.NonceStorage, cookiesEnabled bool, expectedAudience string, maxSkew time.Duration, maxTTL time.Duration, authErrorRedirectPrefix string) (jose.Claims, error) { protocol := "http" if req.Header.Get("X-Forwarded-Proto") == "https" { protocol = "https" } - var token = "" - // Extract token from cookie if enabled. - if cookiesEnabled { - cookieExtractor := oidc.CookieTokenExtractor("access_token") - cookieToken, err := cookieExtractor(req) - if err == nil { - token = cookieToken - } - } + // First, try to find the token in the query params + var token = req.URL.Query().Get("token") + + // Try to extract the token from the header if token == "" { - // Not found in cookie, extract from header headerToken, err := oidc.ExtractBearerToken(req) if err == nil { token = headerToken } } - if token == "" { - // Not found in cookies neither header, extract from query - token = req.URL.Query().Get("token") + // Try to extract token from cookie if enabled. + if token == "" && cookiesEnabled { + cookieExtractor := oidc.CookieTokenExtractor("access_token") + cookieToken, err := cookieExtractor(req) + if err == nil { + token = cookieToken + } } if token == "" { // Not found anywhere - return nil, &authRequiredError{"No JWT found", protocol + "://" + req.Host + req.URL.String()} + return nil, &authRequiredError{"No JWT found", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } // Parse token. @@ -116,43 +114,43 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest now := time.Now().UTC() kid, exists := jwt.Header["kid"] if !exists { - return nil, errors.New("Missing 'kid' claim") + return nil, &authRequiredError{"Missing 'kid' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } iss, exists, err := claims.StringClaim("iss") if !exists || err != nil { - return nil, errors.New("Missing or invalid 'iss' claim") + return nil, &authRequiredError{"Missing or invalid 'iss' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } if expectedAudience != "" { aud, _, err := claims.StringClaim("aud") if err != nil { - return nil, errors.New("Invalid 'aud' claim") + return nil, &authRequiredError{"Invalid 'aud' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } if !verifyAudience(aud, expectedAudience) { - return nil, errors.New("Error - 'aud' claim mismatch") + return nil, &authRequiredError{"Error - 'aud' claim mismatch", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } } exp, exists, err := claims.TimeClaim("exp") if !exists || err != nil { - return nil, errors.New("Missing or invalid 'exp' claim") + return nil, &authRequiredError{"Missing or invalid 'exp' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } if exp.Before(now) { - return nil, &authRequiredError{"Token is expired", protocol + "://" + req.Host + req.URL.String()} + return nil, &authRequiredError{"Token is expired", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } nbf, exists, err := claims.TimeClaim("nbf") if !exists || err != nil || nbf.After(now) { - return nil, errors.New("Missing or invalid 'nbf' claim") + return nil, &authRequiredError{"Missing or invalid 'nbf' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } iat, exists, err := claims.TimeClaim("iat") if !exists || err != nil || iat.Add(-maxSkew).After(now) { - return nil, errors.New("Missing or invalid 'iat' claim") + return nil, &authRequiredError{"Missing or invalid 'iat' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } if exp.Sub(iat) > maxTTL { - return nil, errors.New("Invalid 'exp' claim (too long)") + return nil, &authRequiredError{"Invalid 'exp' claim (too long)", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } jti, exists, err := claims.StringClaim("jti") if !exists || err != nil || !nonceVerifier.Verify(jti, exp) { - return nil, errors.New("Missing or invalid 'jti' claim") + return nil, &authRequiredError{"Missing or invalid 'jti' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } // Verify signature. @@ -161,17 +159,17 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest return nil, err } else if err != nil { log.Errorf("Could not get public key from key server: %s", err) - return nil, errors.New("Unexpected key server error") + return nil, &authRequiredError{"Unexpected key server error", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } verifier, err := publicKey.Verifier() if err != nil { log.Errorf("Could not create JWT verifier for public key '%s': %s", publicKey.ID(), err) - return nil, errors.New("Unexpected verifier initialization failure") + return nil, &authRequiredError{"Unexpected verifier initialization failure", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } if verifier.Verify(jwt.Signature, []byte(jwt.Data())) != nil { - return nil, errors.New("Invalid JWT signature") + return nil, &authRequiredError{"Invalid JWT signature", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} } return claims, nil @@ -182,10 +180,20 @@ func verifyAudience(actual string, expected string) bool { expectedURL, expectedErr := url.ParseRequestURI(expected) if actualErr == nil && expectedErr == nil { // both are URL's - return strings.EqualFold(actualURL.Scheme+"://"+actualURL.Host, expectedURL.Scheme+"://"+expectedURL.Host) + ret := strings.EqualFold(actualURL.Scheme+"://"+actualURL.Host, expectedURL.Scheme+"://"+expectedURL.Host) + if !ret { + log.Errorf("aud verification failed. actual: %s, expected: %s", actual, expected) + } + + return ret } else if actualErr != nil && expectedErr != nil { // both are simple strings - return actual == expected + ret := actual == expected + if !ret { + log.Errorf("aud verification failed. actual: %s, expected: %s", actual, expected) + } + + return ret } else { // One is URL and another is not, which is not valid return false diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 13d5c0c..2825a45 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -276,6 +276,6 @@ func signAndModify(t *testing.T, req *http.Request, p signAndVerifyParams, modif func Verify(req *http.Request, p signAndVerifyParams) error { // Verify. - _, err := jwt.Verify(req, p.services, p.services, p.cookiesEnabled, p.aud, p.maxSkew, p.maxTTL) + _, err := jwt.Verify(req, p.services, p.services, p.cookiesEnabled, p.aud, p.maxSkew, p.maxTTL, "") return err } diff --git a/jwt/proxy_handlers.go b/jwt/proxy_handlers.go index 8c0c576..05a2eec 100644 --- a/jwt/proxy_handlers.go +++ b/jwt/proxy_handlers.go @@ -128,7 +128,7 @@ func NewJWTVerifierHandler(cfg config.VerifierConfig) (*StoppableProxyHandler, e // Create a reverse proxy.Handler that will verify JWT from http.Requests. handler := func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { - signedClaims, err := Verify(r, keyServer, nonceStorage, cfg.CookiesEnabled, cfg.Audience, cfg.MaxSkew, cfg.MaxTTL) + signedClaims, err := Verify(r, keyServer, nonceStorage, cfg.CookiesEnabled, cfg.Audience, cfg.MaxSkew, cfg.MaxTTL, cfg.AuthErrorRedirectURIPrefix) if err != nil { if authErr, ok := err.(*authRequiredError); ok { if redirectUrl != nil { @@ -206,13 +206,23 @@ func NewAuthenticationHandler(cfg config.VerifierConfig) (*StoppableProxyHandler resp = goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusOK, "") resp.Header.Add("Access-Control-Allow-Headers", "authorization,origin,access-control-allow-origin,access-control-request-headers,content-type,access-control-request-method,accept") resp.Header.Add("Access-Control-Allow-Methods", "GET") + } else if !cfg.CookiesEnabled { + return r, goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusForbidden, "Cookies are disabled for this endpoint. You need to provide the access token on your own.") } else { token, err := oidc.ExtractBearerToken(r) if err != nil { return r, goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusForbidden, "No token found in request") } resp = goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusNoContent, "") - cookie := http.Cookie{Name: "access_token", Value: token, HttpOnly: true, Path: "/"} + + var cookiePath string + if cfg.CookiePath == "" { + cookiePath = "/" + } else { + cookiePath = cfg.CookiePath + } + + cookie := http.Cookie{Name: "access_token", Value: token, HttpOnly: true, Path: cookiePath} if redirectUrl.Scheme == "https" { cookie.Secure = true } From a64975fa4de42ca8182b6567daf15c9248320467 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Thu, 29 Aug 2019 21:46:14 +0200 Subject: [PATCH 2/4] Make sure a correct path with no consecutive slashes is produced for redirects. --- jwt/jwt.go | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/jwt/jwt.go b/jwt/jwt.go index b287b78..75cf521 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -96,7 +96,7 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest if token == "" { // Not found anywhere - return nil, &authRequiredError{"No JWT found", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("No JWT found", protocol, authErrorRedirectPrefix, req) } // Parse token. @@ -114,43 +114,43 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest now := time.Now().UTC() kid, exists := jwt.Header["kid"] if !exists { - return nil, &authRequiredError{"Missing 'kid' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing 'kid' claim", protocol, authErrorRedirectPrefix, req) } iss, exists, err := claims.StringClaim("iss") if !exists || err != nil { - return nil, &authRequiredError{"Missing or invalid 'iss' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing or invalid 'iss' claim", protocol, authErrorRedirectPrefix, req) } if expectedAudience != "" { aud, _, err := claims.StringClaim("aud") if err != nil { - return nil, &authRequiredError{"Invalid 'aud' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Invalid 'aud' claim", protocol, authErrorRedirectPrefix, req) } if !verifyAudience(aud, expectedAudience) { - return nil, &authRequiredError{"Error - 'aud' claim mismatch", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Error - 'aud' claim mismatch", protocol, authErrorRedirectPrefix, req) } } exp, exists, err := claims.TimeClaim("exp") if !exists || err != nil { - return nil, &authRequiredError{"Missing or invalid 'exp' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing or invalid 'exp' claim", protocol, authErrorRedirectPrefix, req) } if exp.Before(now) { - return nil, &authRequiredError{"Token is expired", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Token is expired", protocol, authErrorRedirectPrefix, req) } nbf, exists, err := claims.TimeClaim("nbf") if !exists || err != nil || nbf.After(now) { - return nil, &authRequiredError{"Missing or invalid 'nbf' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing or invalid 'nbf' claim", protocol, authErrorRedirectPrefix, req) } iat, exists, err := claims.TimeClaim("iat") if !exists || err != nil || iat.Add(-maxSkew).After(now) { - return nil, &authRequiredError{"Missing or invalid 'iat' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing or invalid 'iat' claim", protocol, authErrorRedirectPrefix, req) } if exp.Sub(iat) > maxTTL { - return nil, &authRequiredError{"Invalid 'exp' claim (too long)", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Invalid 'exp' claim (too long)", protocol, authErrorRedirectPrefix, req) } jti, exists, err := claims.StringClaim("jti") if !exists || err != nil || !nonceVerifier.Verify(jti, exp) { - return nil, &authRequiredError{"Missing or invalid 'jti' claim", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Missing or invalid 'jti' claim", protocol, authErrorRedirectPrefix, req) } // Verify signature. @@ -159,22 +159,37 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest return nil, err } else if err != nil { log.Errorf("Could not get public key from key server: %s", err) - return nil, &authRequiredError{"Unexpected key server error", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Unexpected key server error", protocol, authErrorRedirectPrefix, req) } verifier, err := publicKey.Verifier() if err != nil { log.Errorf("Could not create JWT verifier for public key '%s': %s", publicKey.ID(), err) - return nil, &authRequiredError{"Unexpected verifier initialization failure", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Unexpected verifier initialization failure", protocol, authErrorRedirectPrefix, req) } if verifier.Verify(jwt.Signature, []byte(jwt.Data())) != nil { - return nil, &authRequiredError{"Invalid JWT signature", protocol + "://" + req.Host + authErrorRedirectPrefix + req.URL.String()} + return nil, constructVerificationError("Invalid JWT signature", protocol, authErrorRedirectPrefix, req) } return claims, nil } +func constructVerificationError(message string, protocol string, pathPrefix string, req *http.Request) error { + url := composeRedirectURL(protocol, req, pathPrefix) + return &authRequiredError{message, url} +} + +func composeRedirectURL(protocol string, req *http.Request, pathPrefix string) string { + u := *req.URL + + u.Path = singleJoiningSlash(singleJoiningSlash("/", pathPrefix), u.Path) + u.Host = req.Host + u.Scheme = protocol + + return u.String() +} + func verifyAudience(actual string, expected string) bool { actualURL, actualErr := url.ParseRequestURI(actual) expectedURL, expectedErr := url.ParseRequestURI(expected) From 5d4edca309e88818c9789e30994ff7cda9d3a0bf Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Fri, 30 Aug 2019 15:12:39 +0200 Subject: [PATCH 3/4] AuthErrorREdirectURIPrefix -> PublicBasePath because it better describes what the value actually is. --- config/config.go | 22 +++++++++++----------- jwt/jwt.go | 30 +++++++++++++++--------------- jwt/proxy_handlers.go | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/config/config.go b/config/config.go index d3ec3ba..08a23f4 100644 --- a/config/config.go +++ b/config/config.go @@ -115,17 +115,17 @@ type SignerProxyConfig struct { type VerifierConfig struct { Upstream URL `yaml:"upstream"` // Changed to string to be more JWT spec compliant - it can be either string or URL - Audience string `yaml:"audience"` - CookiesEnabled bool `yaml:"auth_cookies_enabled"` - CookiePath string `yaml:"cookie_path"` - AuthRedirect string `yaml:"auth_redirect_url"` - MaxSkew time.Duration `yaml:"max_skew"` - MaxTTL time.Duration `yaml:"max_ttl"` - KeyServer RegistrableComponentConfig `yaml:"key_server"` - NonceStorage RegistrableComponentConfig `yaml:"nonce_storage"` - Excludes []string `yaml:"excludes"` - ClaimsVerifiers []RegistrableComponentConfig `yaml:"claims_verifiers"` - AuthErrorRedirectURIPrefix string `yaml:"auth_error_redirect_uri_prefix"` + Audience string `yaml:"audience"` + CookiesEnabled bool `yaml:"auth_cookies_enabled"` + CookiePath string `yaml:"cookie_path"` + AuthRedirect string `yaml:"auth_redirect_url"` + MaxSkew time.Duration `yaml:"max_skew"` + MaxTTL time.Duration `yaml:"max_ttl"` + KeyServer RegistrableComponentConfig `yaml:"key_server"` + NonceStorage RegistrableComponentConfig `yaml:"nonce_storage"` + Excludes []string `yaml:"excludes"` + ClaimsVerifiers []RegistrableComponentConfig `yaml:"claims_verifiers"` + PublicBasePath string `yaml:"public_base_path"` } type SignerParams struct { diff --git a/jwt/jwt.go b/jwt/jwt.go index 75cf521..98cfe86 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -68,7 +68,7 @@ func Sign(req *http.Request, key *key.PrivateKey, params config.SignerParams) er return nil } -func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncestorage.NonceStorage, cookiesEnabled bool, expectedAudience string, maxSkew time.Duration, maxTTL time.Duration, authErrorRedirectPrefix string) (jose.Claims, error) { +func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncestorage.NonceStorage, cookiesEnabled bool, expectedAudience string, maxSkew time.Duration, maxTTL time.Duration, publicBasePath string) (jose.Claims, error) { protocol := "http" if req.Header.Get("X-Forwarded-Proto") == "https" { protocol = "https" @@ -96,7 +96,7 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest if token == "" { // Not found anywhere - return nil, constructVerificationError("No JWT found", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("No JWT found", protocol, publicBasePath, req) } // Parse token. @@ -114,43 +114,43 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest now := time.Now().UTC() kid, exists := jwt.Header["kid"] if !exists { - return nil, constructVerificationError("Missing 'kid' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing 'kid' claim", protocol, publicBasePath, req) } iss, exists, err := claims.StringClaim("iss") if !exists || err != nil { - return nil, constructVerificationError("Missing or invalid 'iss' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing or invalid 'iss' claim", protocol, publicBasePath, req) } if expectedAudience != "" { aud, _, err := claims.StringClaim("aud") if err != nil { - return nil, constructVerificationError("Invalid 'aud' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Invalid 'aud' claim", protocol, publicBasePath, req) } if !verifyAudience(aud, expectedAudience) { - return nil, constructVerificationError("Error - 'aud' claim mismatch", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Error - 'aud' claim mismatch", protocol, publicBasePath, req) } } exp, exists, err := claims.TimeClaim("exp") if !exists || err != nil { - return nil, constructVerificationError("Missing or invalid 'exp' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing or invalid 'exp' claim", protocol, publicBasePath, req) } if exp.Before(now) { - return nil, constructVerificationError("Token is expired", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Token is expired", protocol, publicBasePath, req) } nbf, exists, err := claims.TimeClaim("nbf") if !exists || err != nil || nbf.After(now) { - return nil, constructVerificationError("Missing or invalid 'nbf' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing or invalid 'nbf' claim", protocol, publicBasePath, req) } iat, exists, err := claims.TimeClaim("iat") if !exists || err != nil || iat.Add(-maxSkew).After(now) { - return nil, constructVerificationError("Missing or invalid 'iat' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing or invalid 'iat' claim", protocol, publicBasePath, req) } if exp.Sub(iat) > maxTTL { - return nil, constructVerificationError("Invalid 'exp' claim (too long)", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Invalid 'exp' claim (too long)", protocol, publicBasePath, req) } jti, exists, err := claims.StringClaim("jti") if !exists || err != nil || !nonceVerifier.Verify(jti, exp) { - return nil, constructVerificationError("Missing or invalid 'jti' claim", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Missing or invalid 'jti' claim", protocol, publicBasePath, req) } // Verify signature. @@ -159,17 +159,17 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest return nil, err } else if err != nil { log.Errorf("Could not get public key from key server: %s", err) - return nil, constructVerificationError("Unexpected key server error", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Unexpected key server error", protocol, publicBasePath, req) } verifier, err := publicKey.Verifier() if err != nil { log.Errorf("Could not create JWT verifier for public key '%s': %s", publicKey.ID(), err) - return nil, constructVerificationError("Unexpected verifier initialization failure", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Unexpected verifier initialization failure", protocol, publicBasePath, req) } if verifier.Verify(jwt.Signature, []byte(jwt.Data())) != nil { - return nil, constructVerificationError("Invalid JWT signature", protocol, authErrorRedirectPrefix, req) + return nil, constructVerificationError("Invalid JWT signature", protocol, publicBasePath, req) } return claims, nil diff --git a/jwt/proxy_handlers.go b/jwt/proxy_handlers.go index 05a2eec..39c7efd 100644 --- a/jwt/proxy_handlers.go +++ b/jwt/proxy_handlers.go @@ -128,7 +128,7 @@ func NewJWTVerifierHandler(cfg config.VerifierConfig) (*StoppableProxyHandler, e // Create a reverse proxy.Handler that will verify JWT from http.Requests. handler := func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { - signedClaims, err := Verify(r, keyServer, nonceStorage, cfg.CookiesEnabled, cfg.Audience, cfg.MaxSkew, cfg.MaxTTL, cfg.AuthErrorRedirectURIPrefix) + signedClaims, err := Verify(r, keyServer, nonceStorage, cfg.CookiesEnabled, cfg.Audience, cfg.MaxSkew, cfg.MaxTTL, cfg.PublicBasePath) if err != nil { if authErr, ok := err.(*authRequiredError); ok { if redirectUrl != nil { From 8117580f996c3d226508499e9aadf8e4005ba0df Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Mon, 2 Sep 2019 12:06:03 +0200 Subject: [PATCH 4/4] Add descriptions of the new parameters in the README. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 60d8733..6d04435 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,15 @@ jwtproxy: nonce_storage: type: options: + + # a path on which the `access_token` cookie should be stored. This means that the browsers will only use the + # cookie on requests for URLs on the sub paths of the cookie_path. + cookie_path: + + # In case the JWT proxy is placed behind a path-rewriting reverse proxy (such as Kubernetes Ingress) + # the value of this property can be used to modify the "apparent" path of the request as the JWT proxy + # sees it when composing the redirect to be used after the authentication request. + public_base_path: ``` #### Key Registry Key Server