From 3f2fab10e62b13f804d6a54af72539a95fcf1885 Mon Sep 17 00:00:00 2001 From: Benjamin Chess Date: Tue, 16 Apr 2019 14:57:09 -0700 Subject: [PATCH 01/37] check google group based on email address --- CHANGELOG.md | 3 +++ providers/google.go | 14 +++++++++----- providers/google_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f892f6b..9da760b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ - [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer) +- [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess) + - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. + # v3.2.0 ## Release highlights diff --git a/providers/google.go b/providers/google.go index f031bfc..7b8815f 100644 --- a/providers/google.go +++ b/providers/google.go @@ -187,10 +187,8 @@ func userInGroup(service *admin.Service, groups []string, email string) bool { user, err := fetchUser(service, email) if err != nil { logger.Printf("error fetching user: %v", err) - return false + user = nil } - id := user.Id - custID := user.CustomerId for _, group := range groups { members, err := fetchGroupMembers(service, group) @@ -204,13 +202,19 @@ func userInGroup(service *admin.Service, groups []string, email string) bool { } for _, member := range members { + if member.Email == email { + return true + } + if user == nil { + continue + } switch member.Type { case "CUSTOMER": - if member.Id == custID { + if member.Id == user.CustomerId { return true } case "USER": - if member.Id == id { + if member.Id == user.Id { return true } } diff --git a/providers/google_test.go b/providers/google_test.go index 25b375a..0c1725b 100644 --- a/providers/google_test.go +++ b/providers/google_test.go @@ -3,12 +3,15 @@ package providers import ( "encoding/base64" "encoding/json" + "fmt" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" + + admin "google.golang.org/api/admin/directory/v1" ) func newRedeemServer(body []byte) (*url.URL, *httptest.Server) { @@ -179,3 +182,37 @@ func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) { } } + +func TestGoogleProviderUserInGroup(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/users/member-by-email@example.com" { + fmt.Fprintln(w, "{}") + } else if r.URL.Path == "/users/non-member-by-email@example.com" { + fmt.Fprintln(w, "{}") + } else if r.URL.Path == "/users/member-by-id@example.com" { + fmt.Fprintln(w, "{\"id\": \"member-id\"}") + } else if r.URL.Path == "/users/non-member-by-id@example.com" { + fmt.Fprintln(w, "{\"id\": \"non-member-id\"}") + } else if r.URL.Path == "/groups/group@example.com/members" { + fmt.Fprintln(w, "{\"members\": [{\"email\": \"member-by-email@example.com\"}, {\"id\": \"member-id\", \"type\": \"USER\"}]}") + } + })) + defer ts.Close() + + client := ts.Client() + service, err := admin.New(client) + service.BasePath = ts.URL + assert.Equal(t, nil, err) + + result := userInGroup(service, []string{"group@example.com"}, "member-by-email@example.com") + assert.True(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "member-by-id@example.com") + assert.True(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "non-member-by-id@example.com") + assert.False(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "non-member-by-email@example.com") + assert.False(t, result) +} From 7179c5796aca5798f90e6962fedcd9b3c6577cee Mon Sep 17 00:00:00 2001 From: Benjamin Chess Date: Wed, 8 May 2019 08:29:38 -0700 Subject: [PATCH 02/37] make unable to fetch user a warning message --- providers/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/google.go b/providers/google.go index 7b8815f..02fd35f 100644 --- a/providers/google.go +++ b/providers/google.go @@ -186,7 +186,7 @@ func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Serv func userInGroup(service *admin.Service, groups []string, email string) bool { user, err := fetchUser(service, email) if err != nil { - logger.Printf("error fetching user: %v", err) + logger.Printf("Warning: unable to fetch user: %v", err) user = nil } From 9e59b4f62e4abd2812699ba9ac2d00ae83d43dfd Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Fri, 7 Jun 2019 13:50:44 +1000 Subject: [PATCH 03/37] Restructure so that serving data from upstream is only done when explicity allowed, rather than as implicit dangling else --- oauthproxy.go | 78 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 389b2a9..dfc2086 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -47,6 +47,11 @@ var SignatureHeaders = []string{ "Gap-Auth", } +var ( + // ErrNeedsLogin means the user should be redirected to the login page + ErrNeedsLogin = errors.New("redirect to login page") +) + // OAuthProxy is the main authentication proxy type OAuthProxy struct { CookieSeed string @@ -477,20 +482,19 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool { } // IsWhitelistedRequest is used to check if auth should be skipped for this request -func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) { +func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool { isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS" return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) } // IsWhitelistedPath is used to check if the request path is allowed without auth -func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) { +func (p *OAuthProxy) IsWhitelistedPath(path string) bool { for _, u := range p.compiledRegex { - ok = u.MatchString(path) - if ok { - return + if u.MatchString(path) { + return true } } - return + return false } func getRemoteAddr(req *http.Request) (s string) { @@ -641,10 +645,11 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { - status := p.Authenticate(rw, req) - if status == http.StatusAccepted { + _, err := p.getAuthenticatedSession(rw, req) + switch err { + case nil: rw.WriteHeader(http.StatusAccepted) - } else { + default: http.Error(rw, "unauthorized request", http.StatusUnauthorized) } } @@ -652,25 +657,40 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) // Proxy proxies the user request if the user is authenticated else it prompts // them to authenticate func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { - status := p.Authenticate(rw, req) - if status == http.StatusInternalServerError { - p.ErrorPage(rw, http.StatusInternalServerError, - "Internal Error", "Internal Error") - } else if status == http.StatusForbidden { + session, err := p.getAuthenticatedSession(rw, req) + switch err { + case nil: + // we are authenticated + p.addHeadersForProxying(rw, req, session) + p.serveMux.ServeHTTP(rw, req) + + case ErrNeedsLogin: + // we need to send the user to a login screen + if isAjax(req) { + // no point redirecting an AJAX request + p.ErrorJSON(rw, http.StatusUnauthorized) + return + } + if p.SkipProviderButton { p.OAuthStart(rw, req) } else { p.SignInPage(rw, req, http.StatusForbidden) } - } else if status == http.StatusUnauthorized { - p.ErrorJSON(rw, status) - } else { - p.serveMux.ServeHTTP(rw, req) + + default: + // unknown error + logger.Printf("Unexpected internal error: %s", err) + p.ErrorPage(rw, http.StatusInternalServerError, + "Internal Error", "Internal Error") } + } -// Authenticate checks whether a user is authenticated -func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int { +// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so +// Returns nil, ErrNeedsLogin if user needs to login. +// Set-Cookie headers may be set on the response as a side-effect of calling this method. +func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) { var saveSession, clearSession, revalidated bool remoteAddr := getRemoteAddr(req) @@ -720,7 +740,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int err = p.SaveSession(rw, req, session) if err != nil { logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err) - return http.StatusInternalServerError + return nil, err } } @@ -736,15 +756,14 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if session == nil { - // Check if is an ajax request and return unauthorized to avoid a redirect - // to the login page - if p.isAjax(req) { - return http.StatusUnauthorized - } - return http.StatusForbidden + return nil, ErrNeedsLogin } - // At this point, the user is authenticated. proxy normally + return session, nil +} + +// addHeadersForProxying adds the appropriate headers the request / response for proxying +func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) { if p.PassBasicAuth { req.SetBasicAuth(session.User, p.BasicAuthPassword) req.Header["X-Forwarded-User"] = []string{session.User} @@ -781,7 +800,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } else { rw.Header().Set("GAP-Auth", session.Email) } - return http.StatusAccepted } // CheckBasicAuth checks the requests Authorization header for basic auth @@ -815,7 +833,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionStat } // isAjax checks if a request is an ajax request -func (p *OAuthProxy) isAjax(req *http.Request) bool { +func isAjax(req *http.Request) bool { acceptValues, ok := req.Header["accept"] if !ok { acceptValues = req.Header["Accept"] From f35c82bb0f7bb3877f7e60e6c2c62c5d1c7f9c99 Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Fri, 7 Jun 2019 14:25:12 +1000 Subject: [PATCH 04/37] The AuthOnly path also needs the response headers set --- oauthproxy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauthproxy.go b/oauthproxy.go index dfc2086..687a9b2 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -645,9 +645,11 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { - _, err := p.getAuthenticatedSession(rw, req) + session, err := p.getAuthenticatedSession(rw, req) switch err { case nil: + // we are authenticated + p.addHeadersForProxying(rw, req, session) rw.WriteHeader(http.StatusAccepted) default: http.Error(rw, "unauthorized request", http.StatusUnauthorized) From 7a8fb58ad1a2ed1984e5b6be305b68de3fe1c608 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Fri, 14 Jun 2019 11:33:05 -0400 Subject: [PATCH 05/37] Only validate tokens if ValidateURL resolves to a non-empty string Fix an unsupported protocol scheme error when validating tokens by ensuring that the ValidateURL generates a non-empty string. The Azure provider doesn't define any ValidateURL and therefore uses the default value of `url.Parse("")` which is not `nil`. The following log summary shows the issue: 2019/06/14 12:26:04 oauthproxy.go:799: 10.244.1.3:34112 ("10.244.1.1") refreshing 16h26m29s old session cookie for Session{email:jonas.fonseca@example.com user:jonas.fonseca token:true} (refresh after 1h0m0s) 2019/06/14 12:26:04 internal_util.go:60: GET ?access_token=eyJ0... 2019/06/14 12:26:04 internal_util.go:61: token validation request failed: Get ?access_token=eyJ0...: unsupported protocol scheme "" 2019/06/14 12:26:04 oauthproxy.go:822: 10.244.1.3:34112 ("10.244.1.1") removing session. error validating Session{email:jonas.fonseca@example.com user:jonas.fonseca token:true} --- CHANGELOG.md | 1 + providers/internal_util.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3fc3d..6cfd7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ - [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer) - [#170](https://github.com/pusher/oauth2_proxy/pull/170) Restore binary tarball contents to be compatible with bitlys original tarballs (@zeha) +- [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas) # v3.2.0 diff --git a/providers/internal_util.go b/providers/internal_util.go index 7144dee..849e2c7 100644 --- a/providers/internal_util.go +++ b/providers/internal_util.go @@ -47,7 +47,7 @@ func stripParam(param, endpoint string) string { // validateToken returns true if token is valid func validateToken(p Provider, accessToken string, header http.Header) bool { - if accessToken == "" || p.Data().ValidateURL == nil { + if accessToken == "" || p.Data().ValidateURL == nil || p.Data().ValidateURL.String() == "" { return false } endpoint := p.Data().ValidateURL.String() From d69560d0200e778bfc1a5db372f4a27f6947f4fb Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Sat, 15 Jun 2019 18:48:27 +1000 Subject: [PATCH 06/37] No need for case when only 2 conditions --- CHANGELOG.md | 1 + oauthproxy.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3fc3d..92522cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ## Changes since v3.2.0 +- [#180](https://github.com/pusher/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). - [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). - Includes fix for potential signature checking issue when OIDC discovery is skipped. - [#155](https://github.com/pusher/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) diff --git a/oauthproxy.go b/oauthproxy.go index 687a9b2..ef97415 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -646,14 +646,14 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { session, err := p.getAuthenticatedSession(rw, req) - switch err { - case nil: - // we are authenticated - p.addHeadersForProxying(rw, req, session) - rw.WriteHeader(http.StatusAccepted) - default: + if err != nil { http.Error(rw, "unauthorized request", http.StatusUnauthorized) + return } + + // we are authenticated + p.addHeadersForProxying(rw, req, session) + rw.WriteHeader(http.StatusAccepted) } // Proxy proxies the user request if the user is authenticated else it prompts From 8083501da68e19c8633726bcab87ea680d9048a0 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 17 Jan 2019 12:49:14 -0800 Subject: [PATCH 07/37] Support JWT Bearer Token and Pass through --- docs/configuration/configuration.md | 2 + main.go | 3 + oauthproxy.go | 187 ++++++++++++++++++++++------ options.go | 63 ++++++++-- 4 files changed, 213 insertions(+), 42 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d631eaf..1295269 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -41,6 +41,7 @@ Usage of oauth2_proxy: -custom-templates-dir string: path to custom html templates -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email + -extra-jwt-issuers: if -skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json) -flush-interval: period between flushing response buffers when streaming responses (default "1s") -footer string: custom footer string. Use "-" to disable default footer. -gcp-healthchecks: will enable /liveness_check, /readiness_check, and / (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses (default false) @@ -89,6 +90,7 @@ Usage of oauth2_proxy: -signature-key string: GAP-Signature request signature key (algorithm:secretkey) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) + -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS diff --git a/main.go b/main.go index a66c4fc..8c86271 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ func main() { whitelistDomains := StringArray{} upstreams := StringArray{} skipAuthRegex := StringArray{} + jwtIssuers := StringArray{} googleGroups := StringArray{} redisSentinelConnectionURLs := StringArray{} @@ -48,6 +49,8 @@ func main() { flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses") + flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens") + flagSet.Var(&jwtIssuers, "extra-jwt-issuers", "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)") flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") diff --git a/oauthproxy.go b/oauthproxy.go index ef97415..f7bbef3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -1,6 +1,7 @@ package main import ( + "context" b64 "encoding/base64" "errors" "fmt" @@ -13,6 +14,7 @@ import ( "strings" "time" + "github.com/coreos/go-oidc" "github.com/mbland/hmacauth" "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/logger" @@ -92,6 +94,8 @@ type OAuthProxy struct { PassAuthorization bool skipAuthRegex []string skipAuthPreflight bool + skipJwtBearerTokens bool + jwtBearerVerifiers []*oidc.IDTokenVerifier compiledRegex []*regexp.Regexp templates *template.Template Footer string @@ -206,6 +210,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { logger.Printf("compiled skip-auth-regex => %q", u) } + if opts.SkipJwtBearerTokens { + logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL) + for _, issuer := range opts.ExtraJwtIssuers { + logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer) + } + } redirectURL := opts.redirectURL if redirectURL.Path == "" { redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) @@ -239,25 +249,27 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), - ProxyPrefix: opts.ProxyPrefix, - provider: opts.provider, - sessionStore: opts.sessionStore, - serveMux: serveMux, - redirectURL: redirectURL, - whitelistDomains: opts.WhitelistDomains, - skipAuthRegex: opts.SkipAuthRegex, - skipAuthPreflight: opts.SkipAuthPreflight, - compiledRegex: opts.CompiledRegex, - SetXAuthRequest: opts.SetXAuthRequest, - PassBasicAuth: opts.PassBasicAuth, - PassUserHeaders: opts.PassUserHeaders, - BasicAuthPassword: opts.BasicAuthPassword, - PassAccessToken: opts.PassAccessToken, - SetAuthorization: opts.SetAuthorization, - PassAuthorization: opts.PassAuthorization, - SkipProviderButton: opts.SkipProviderButton, - templates: loadTemplates(opts.CustomTemplatesDir), - Footer: opts.Footer, + ProxyPrefix: opts.ProxyPrefix, + provider: opts.provider, + sessionStore: opts.sessionStore, + serveMux: serveMux, + redirectURL: redirectURL, + whitelistDomains: opts.WhitelistDomains, + skipAuthRegex: opts.SkipAuthRegex, + skipAuthPreflight: opts.SkipAuthPreflight, + skipJwtBearerTokens: opts.SkipJwtBearerTokens, + jwtBearerVerifiers: opts.jwtBearerVerifiers, + compiledRegex: opts.CompiledRegex, + SetXAuthRequest: opts.SetXAuthRequest, + PassBasicAuth: opts.PassBasicAuth, + PassUserHeaders: opts.PassUserHeaders, + BasicAuthPassword: opts.BasicAuthPassword, + PassAccessToken: opts.PassAccessToken, + SetAuthorization: opts.SetAuthorization, + PassAuthorization: opts.PassAuthorization, + SkipProviderButton: opts.SkipProviderButton, + templates: loadTemplates(opts.CustomTemplatesDir), + Footer: opts.Footer, } } @@ -693,26 +705,42 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { // Returns nil, ErrNeedsLogin if user needs to login. // Set-Cookie headers may be set on the response as a side-effect of calling this method. func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) { + var session *sessionsapi.SessionState + var err error var saveSession, clearSession, revalidated bool + + if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" { + session, err = p.GetJwtSession(req) + if err != nil { + logger.Printf("Error validating JWT token from Authorization header: %s", err) + } + if session != nil { + saveSession = false + } + } + remoteAddr := getRemoteAddr(req) - - session, err := p.LoadCookiedSession(req) - if err != nil { - logger.Printf("Error loading cookied session: %s", err) - } - if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { - logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) - saveSession = true + if session == nil { + session, err = p.LoadCookiedSession(req) + if err != nil { + logger.Printf("Error loading cookied session: %s", err) + } + if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { + logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) + saveSession = true + } } - var ok bool - if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil { - logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session) - clearSession = true - session = nil - } else if ok { - saveSession = true - revalidated = true + if session != nil { + var ok bool + if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil { + logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session) + clearSession = true + session = nil + } else if ok { + saveSession = true + revalidated = true + } } if session != nil && session.IsExpired() { @@ -854,3 +882,92 @@ func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) { rw.Header().Set("Content-Type", applicationJSON) rw.WriteHeader(code) } + +// GetJwtSession loads a session based on a JWT token in the authorization header. +func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState, error) { + rawBearerToken, err := p.findBearerToken(req) + if err != nil { + return nil, err + } + + ctx := context.Background() + var session *sessionsapi.SessionState + for _, verifier := range p.jwtBearerVerifiers { + bearerToken, err := verifier.Verify(ctx, rawBearerToken) + + if err != nil { + logger.Printf("failed to verify bearer token: %v", err) + continue + } + + var claims struct { + Email string `json:"email"` + Verified *bool `json:"email_verified"` + } + + if err := bearerToken.Claims(&claims); err != nil { + return nil, fmt.Errorf("failed to parse bearer token claims: %v", err) + } + + if claims.Email == "" { + return nil, fmt.Errorf("id_token did not contain an email") + } + + if claims.Verified != nil && !*claims.Verified { + return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) + } + user := strings.Split(claims.Email, "@")[0] + + session = &sessionsapi.SessionState{ + AccessToken: rawBearerToken, + IDToken: rawBearerToken, + RefreshToken: "", + ExpiresOn: bearerToken.Expiry, + Email: claims.Email, + User: user, + } + return session, nil + } + return nil, fmt.Errorf("unable to verify jwt token %s", req.Header.Get("Authorization")) +} + +// findBearerToken finds a valid JWT token from the Authorization header of a given request. +func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { + auth := req.Header.Get("Authorization") + s := strings.SplitN(auth, " ", 2) + if len(s) != 2 { + return "", fmt.Errorf("invalid authorization header %s", auth) + } + + var rawBearerToken string + if s[0] == "Bearer" { + rawBearerToken = s[1] + } else if s[0] == "Basic" { + // Check if we have a Bearer token masquerading in Basic + b, err := b64.StdEncoding.DecodeString(s[1]) + if err != nil { + return "", err + } + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + return "", fmt.Errorf("invalid format %s", b) + } + user, password := pair[0], pair[1] + + // check user, user+password, or just password for a token + jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`) + if jwtRegex.MatchString(user) { + // Support blank passwords or magic `x-oauth-basic` passwords - nothing else + if password == "" || password == "x-oauth-basic" { + rawBearerToken = user + } + } else if jwtRegex.MatchString(password) { + // support passwords and ignore user + rawBearerToken = password + } + } else { + return "", fmt.Errorf("invalid authorization header %s", auth) + } + + return rawBearerToken, nil +} diff --git a/options.go b/options.go index 0460bce..331c8f6 100644 --- a/options.go +++ b/options.go @@ -61,6 +61,8 @@ type Options struct { Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"` SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"` + SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` + ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"` BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"` @@ -110,13 +112,15 @@ type Options struct { GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"` // internal values that are set after config validation - redirectURL *url.URL - proxyURLs []*url.URL - CompiledRegex []*regexp.Regexp - provider providers.Provider - sessionStore sessionsapi.SessionStore - signatureData *SignatureData - oidcVerifier *oidc.IDTokenVerifier + redirectURL *url.URL + proxyURLs []*url.URL + CompiledRegex []*regexp.Regexp + provider providers.Provider + sessionStore sessionsapi.SessionStore + signatureData *SignatureData + oidcVerifier *oidc.IDTokenVerifier + jwtIssuers [][]string + jwtBearerVerifiers []*oidc.IDTokenVerifier } // SignatureData holds hmacauth signature hash and key @@ -244,6 +248,38 @@ func (o *Options) Validate() error { } } + if o.SkipJwtBearerTokens { + // If we are using an oidc provider, go ahead and add that provider to the list + if o.oidcVerifier != nil { + o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier) + } + // Configure extra issuers + if len(o.ExtraJwtIssuers) > 0 { + msgs = parseJwtIssuers(o, msgs) + for _, pair := range o.jwtIssuers { + issuer, audience := pair[0], pair[1] + config := &oidc.Config{ + ClientID: audience, + } + // Try as an OpenID Connect Provider first + var verifier *oidc.IDTokenVerifier + provider, err := oidc.NewProvider(context.Background(), issuer) + if err != nil { + // Try as JWKS URI + jwksURI := strings.TrimSuffix(issuer, "/") + "/.well-known/jwks.json" + _, err := http.NewRequest("GET", jwksURI, nil) + if err != nil { + return err + } + verifier = oidc.NewVerifier(issuer, oidc.NewRemoteKeySet(context.Background(), jwksURI), config) + } else { + verifier = provider.Verifier(config) + } + o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier) + } + } + } + o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs) for _, u := range o.Upstreams { @@ -430,6 +466,19 @@ func parseSignatureKey(o *Options, msgs []string) []string { return msgs } +func parseJwtIssuers(o *Options, msgs []string) []string { + for _, jwtVerifier := range o.ExtraJwtIssuers { + components := strings.Split(jwtVerifier, "=") + if len(components) < 2 { + return append(msgs, "invalid jwt verifier uri=audience spec: "+ + jwtVerifier) + } + uri, audience := components[0], strings.Join(components[1:], "=") + o.jwtIssuers = append(o.jwtIssuers, []string{uri, audience}) + } + return msgs +} + func validateCookieName(o *Options, msgs []string) []string { cookie := &http.Cookie{Name: o.CookieName} if cookie.String() == "" { From b895f49c52ef3d37fa4ab55e656b5e279d3237d9 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Tue, 12 Feb 2019 10:32:26 -0800 Subject: [PATCH 08/37] Use idToken expiry because that's the time checked for refresh RefreshSessionIfNeeded checks the token expiry, we want to use the ID token's expiry --- providers/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/oidc.go b/providers/oidc.go index 08ea082..b0d2dda 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -128,7 +128,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok IDToken: rawIDToken, RefreshToken: token.RefreshToken, CreatedAt: time.Now(), - ExpiresOn: token.Expiry, + ExpiresOn: idToken.Expiry, Email: claims.Email, User: claims.Subject, }, nil From 8413c30c26491dc2f234f88a146fdb7f4cf7db8d Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 14 Feb 2019 15:00:49 -0800 Subject: [PATCH 09/37] Update changelog with info about -skip-jwt-bearer-tokens --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef731cb..136775f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ ## Changes since v3.2.0 +- [#65](https://github.com/pusher/oauth2_proxy/pull/65) Improvements to authenticate requests with a JWT bearer token in the `Authorization` header via + the `-skip-jwt-bearer-token` options. + - Additional verifiers can be configured via the `-extra-jwt-issuers` flag if the JWT issuers is either an OpenID provider or has a JWKS URL + (e.g. `https://example.com/.well-known/jwks.json`). - [#180](https://github.com/pusher/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). - [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). - Includes fix for potential signature checking issue when OIDC discovery is skipped. From 187960e9d884b38644aa8e015b9cec9c548c9a2e Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 24 Apr 2019 08:25:29 -0700 Subject: [PATCH 10/37] Improve token pattern matching Unit tests for token discovery --- oauthproxy.go | 12 ++++----- oauthproxy_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index f7bbef3..714c7a3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -712,7 +712,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" { session, err = p.GetJwtSession(req) if err != nil { - logger.Printf("Error validating JWT token from Authorization header: %s", err) + logger.Printf("Error retrieving session from token in Authorization header: %s", err) } if session != nil { saveSession = false @@ -938,9 +938,9 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { if len(s) != 2 { return "", fmt.Errorf("invalid authorization header %s", auth) } - + jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`) var rawBearerToken string - if s[0] == "Bearer" { + if s[0] == "Bearer" && jwtRegex.MatchString(s[1]) { rawBearerToken = s[1] } else if s[0] == "Basic" { // Check if we have a Bearer token masquerading in Basic @@ -955,7 +955,6 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { user, password := pair[0], pair[1] // check user, user+password, or just password for a token - jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`) if jwtRegex.MatchString(user) { // Support blank passwords or magic `x-oauth-basic` passwords - nothing else if password == "" || password == "x-oauth-basic" { @@ -965,8 +964,9 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { // support passwords and ignore user rawBearerToken = password } - } else { - return "", fmt.Errorf("invalid authorization header %s", auth) + } + if rawBearerToken == "" { + return "", fmt.Errorf("no valid bearer token found in authorization header") } return rawBearerToken, nil diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 1d09bbb..493ce72 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -3,6 +3,7 @@ package main import ( "crypto" "encoding/base64" + "fmt" "io" "io/ioutil" "net" @@ -1132,3 +1133,65 @@ func TestClearSingleCookie(t *testing.T) { assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries") } + +func TestFindJwtBearerToken(t *testing.T) { + p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"} + getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} + + validToken := "eyJfoobar.eyJfoobar.12345asdf" + var token string + + // Bearer + getReq.Header = map[string][]string{ + "Authorization": {fmt.Sprintf("Bearer %s", validToken)}, + } + + token, _ = p.findBearerToken(getReq) + assert.Equal(t, validToken, token) + + // Basic - no password + getReq.SetBasicAuth(token, "") + token, _ = p.findBearerToken(getReq) + assert.Equal(t, validToken, token) + + // Basic - sentinel password + getReq.SetBasicAuth(token, "x-oauth-basic") + token, _ = p.findBearerToken(getReq) + assert.Equal(t, validToken, token) + + // Basic - any username, password matching jwt pattern + getReq.SetBasicAuth("any-username-you-could-wish-for", token) + token, _ = p.findBearerToken(getReq) + assert.Equal(t, validToken, token) + + failures := []string{ + // Too many parts + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA", + // Not enough parts + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA", + // Invalid encrypted key + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA", + // Invalid IV + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA", + // Invalid ciphertext + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA", + // Invalid tag + "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////", + // Invalid header + "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA", + // Invalid header + "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA", + // Missing alc/enc params + "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA", + } + + for _, failure := range failures { + getReq.Header = map[string][]string{ + "Authorization": {fmt.Sprintf("Bearer %s", failure)}, + } + _, err := p.findBearerToken(getReq) + assert.Error(t, err) + } + + fmt.Printf("%s", token) +} From 69cb34a04e6d00d9dca03940cddd78e99fcef4dd Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 26 Apr 2019 19:16:45 -0700 Subject: [PATCH 11/37] Add unit tests for JWT -> session translation --- oauthproxy_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 493ce72..042e6a5 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1,9 +1,11 @@ package main import ( + "context" "crypto" "encoding/base64" "fmt" + "github.com/coreos/go-oidc" "io" "io/ioutil" "net" @@ -1134,6 +1136,53 @@ func TestClearSingleCookie(t *testing.T) { assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries") } +type NoOpKeySet struct { +} + +func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) { + splitStrings := strings.Split(jwt, ".") + payloadString := splitStrings[1] + jsonString, err := base64.RawURLEncoding.DecodeString(payloadString) + return []byte(jsonString), err +} + +func TestGetJwtSession(t *testing.T) { + /* token payload: + { + "sub": "1234567890", + "aud": "https://test.myapp.com", + "name": "John Doe", + "email": "john@example.com", + "iss": "https://issuer.example.com", + "iat": 1553691215, + "exp": 1912151821 + } + */ + goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." + + "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" + + "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" + + "E1LCJleHAiOjE5MTIxNTE4MjF9." + + "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" + + "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8" + + keyset := NoOpKeySet{} + verifier := oidc.NewVerifier("https://issuer.example.com", keyset, + &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) + p := OAuthProxy{} + p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) + getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} + + // Bearer + getReq.Header = map[string][]string{ + "Authorization": {fmt.Sprintf("Bearer %s", goodJwt)}, + } + session, _ := p.GetJwtSession(getReq) + assert.Equal(t, session.User, "john") + assert.Equal(t, session.Email, "john@example.com") + assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) + assert.Equal(t, session.IDToken, goodJwt) +} + func TestFindJwtBearerToken(t *testing.T) { p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"} getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} From 1ff74d322a112331f0dd62524b39b1a908ce8e23 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 26 Apr 2019 19:47:53 -0700 Subject: [PATCH 12/37] Fix imports --- oauthproxy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 042e6a5..0704596 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -5,7 +5,6 @@ import ( "crypto" "encoding/base64" "fmt" - "github.com/coreos/go-oidc" "io" "io/ioutil" "net" @@ -17,6 +16,7 @@ import ( "testing" "time" + "github.com/coreos/go-oidc" "github.com/mbland/hmacauth" "github.com/pusher/oauth2_proxy/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" From 10f65e03814afd55534fafc828860348d883dbe8 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Tue, 30 Apr 2019 14:06:11 -0700 Subject: [PATCH 13/37] Add a more realistic test for JWT passthrough --- oauthproxy_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 0704596..bff3682 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1170,17 +1170,67 @@ func TestGetJwtSession(t *testing.T) { &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) p := OAuthProxy{} p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) - getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} + + req, _ := http.NewRequest("GET", "/", strings.NewReader("")) + authHeader := fmt.Sprintf("Bearer %s", goodJwt) + req.Header = map[string][]string{ + "Authorization": {authHeader}, + } // Bearer - getReq.Header = map[string][]string{ - "Authorization": {fmt.Sprintf("Bearer %s", goodJwt)}, - } - session, _ := p.GetJwtSession(getReq) + session, _ := p.GetJwtSession(req) assert.Equal(t, session.User, "john") assert.Equal(t, session.Email, "john@example.com") assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) assert.Equal(t, session.IDToken, goodJwt) + + jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%#v", r) + var payload string + payload = r.Header.Get("Authorization") + if payload == "" { + payload = "No Authorization header found." + } + w.WriteHeader(200) + w.Write([]byte(payload)) + })) + + opts := NewOptions() + opts.Upstreams = append(opts.Upstreams, jwtProviderServer.URL) + opts.PassAuthorization = true + opts.SetAuthorization = true + opts.SetXAuthRequest = true + opts.CookieSecret = "0123456789abcdef0123" + opts.SkipJwtBearerTokens = true + opts.Validate() + + // We can't actually use opts.Validate() because it will attempt to find a jwks URI + opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) + + providerURL, _ := url.Parse(jwtProviderServer.URL) + const emailAddress = "john@example.com" + + opts.provider = NewTestProvider(providerURL, emailAddress) + jwtTestProxy := NewOAuthProxy(opts, func(email string) bool { + return email == emailAddress + }) + + rw := httptest.NewRecorder() + jwtTestProxy.ServeHTTP(rw, req) + if rw.Code >= 400 { + t.Fatalf("expected 3xx got %d", rw.Code) + } + + // Check PassAuthorization, should overwrite Basic header + assert.Equal(t, req.Header.Get("Authorization"), authHeader) + assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john") + assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") + + // SetAuthorization and SetXAuthRequest + assert.Equal(t, rw.Header().Get("Authorization"), authHeader) + assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john") + assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") + } func TestFindJwtBearerToken(t *testing.T) { From 79acef90366f24d3e898c90d78cfe19070492629 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 09:18:54 -0700 Subject: [PATCH 14/37] Clarify skip-jwt-bearer-tokens default and add env tags --- main.go | 2 +- options.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 8c86271..054bb30 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,7 @@ func main() { flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses") - flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens") + flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)") flagSet.Var(&jwtIssuers, "extra-jwt-issuers", "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)") flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") diff --git a/options.go b/options.go index 331c8f6..a8b0330 100644 --- a/options.go +++ b/options.go @@ -61,8 +61,8 @@ type Options struct { Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"` SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"` - SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` - ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` + SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"` + ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"` PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"` BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"` From 58b06ce761e0b706c9ba7e3081aa0441e75527de Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 09:22:25 -0700 Subject: [PATCH 15/37] Fall back to using sub if email is none (as in PR #57) --- oauthproxy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oauthproxy.go b/oauthproxy.go index 714c7a3..e79793b 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -901,6 +901,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState } var claims struct { + Subject string `json:"sub"` Email string `json:"email"` Verified *bool `json:"email_verified"` } @@ -910,7 +911,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState } if claims.Email == "" { - return nil, fmt.Errorf("id_token did not contain an email") + claims.Email = claims.Subject } if claims.Verified != nil && !*claims.Verified { From 350c1cd127515bea8b617944b0d2621d7017ea95 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 10:00:54 -0700 Subject: [PATCH 16/37] Use JwtIssuer struct when parsing --- options.go | 68 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/options.go b/options.go index a8b0330..511666b 100644 --- a/options.go +++ b/options.go @@ -119,7 +119,6 @@ type Options struct { sessionStore sessionsapi.SessionStore signatureData *SignatureData oidcVerifier *oidc.IDTokenVerifier - jwtIssuers [][]string jwtBearerVerifiers []*oidc.IDTokenVerifier } @@ -172,6 +171,12 @@ func NewOptions() *Options { } } +// JwtIssuer hold parsed JWT issuer info that's used to construct a verifier. +type JwtIssuer struct { + issuerURI string + audience string +} + func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) { parsed, err := url.Parse(toParse) if err != nil { @@ -255,25 +260,12 @@ func (o *Options) Validate() error { } // Configure extra issuers if len(o.ExtraJwtIssuers) > 0 { - msgs = parseJwtIssuers(o, msgs) - for _, pair := range o.jwtIssuers { - issuer, audience := pair[0], pair[1] - config := &oidc.Config{ - ClientID: audience, - } - // Try as an OpenID Connect Provider first - var verifier *oidc.IDTokenVerifier - provider, err := oidc.NewProvider(context.Background(), issuer) + var jwtIssuers []JwtIssuer + jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs) + for _, jwtIssuer := range jwtIssuers { + verifier, err := newVerifierFromJwtIssuer(jwtIssuer) if err != nil { - // Try as JWKS URI - jwksURI := strings.TrimSuffix(issuer, "/") + "/.well-known/jwks.json" - _, err := http.NewRequest("GET", jwksURI, nil) - if err != nil { - return err - } - verifier = oidc.NewVerifier(issuer, oidc.NewRemoteKeySet(context.Background(), jwksURI), config) - } else { - verifier = provider.Verifier(config) + msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err)) } o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier) } @@ -466,17 +458,43 @@ func parseSignatureKey(o *Options, msgs []string) []string { return msgs } -func parseJwtIssuers(o *Options, msgs []string) []string { - for _, jwtVerifier := range o.ExtraJwtIssuers { +// parseJwtIssuers takes in an array of strings in the form of issuer=audience +// and parses to an array of JwtIssuer structs. +func parseJwtIssuers(issuers []string, msgs []string) ([]JwtIssuer, []string) { + var parsedIssuers []JwtIssuer + for _, jwtVerifier := range issuers { components := strings.Split(jwtVerifier, "=") if len(components) < 2 { - return append(msgs, "invalid jwt verifier uri=audience spec: "+ - jwtVerifier) + msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier)) + continue } uri, audience := components[0], strings.Join(components[1:], "=") - o.jwtIssuers = append(o.jwtIssuers, []string{uri, audience}) + parsedIssuers = append(parsedIssuers, JwtIssuer{issuerURI: uri, audience: audience}) } - return msgs + return parsedIssuers, msgs +} + +// newVerifierFromJwtIssuer takes in issuer information in JwtIssuer info and returns +// a verifier for that issuer. +func newVerifierFromJwtIssuer(jwtIssuer JwtIssuer) (*oidc.IDTokenVerifier, error) { + config := &oidc.Config{ + ClientID: jwtIssuer.audience, + } + // Try as an OpenID Connect Provider first + var verifier *oidc.IDTokenVerifier + provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI) + if err != nil { + // Try as JWKS URI + jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json" + _, err := http.NewRequest("GET", jwksURI, nil) + if err != nil { + return nil, err + } + verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config) + } else { + verifier = provider.Verifier(config) + } + return verifier, nil } func validateCookieName(o *Options, msgs []string) []string { From 54d91c69cca9b90e04d4fd194760a6b0f5c687b9 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 10:19:00 -0700 Subject: [PATCH 17/37] Use logger instead of log --- oauthproxy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index bff3682..decae03 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1185,7 +1185,7 @@ func TestGetJwtSession(t *testing.T) { assert.Equal(t, session.IDToken, goodJwt) jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("%#v", r) + logger.Printf("%#v", r) var payload string payload = r.Header.Get("Authorization") if payload == "" { From 48dbb391bc1103461bfb4c84a93de2c97737fb86 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 20 May 2019 15:24:59 -0700 Subject: [PATCH 18/37] Move around CHANGELOG.md update --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 136775f..6858187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,6 @@ - [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer) - [#170](https://github.com/pusher/oauth2_proxy/pull/170) Restore binary tarball contents to be compatible with bitlys original tarballs (@zeha) - [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas) - - [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess) - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. From 2f6dcf3b5f93ec3439e8e87298bb97688862ac34 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 5 Jun 2019 16:08:34 -0700 Subject: [PATCH 19/37] Move refreshing code to block acquiring cookied session --- oauthproxy.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index e79793b..e0f5e48 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -725,21 +725,21 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R if err != nil { logger.Printf("Error loading cookied session: %s", err) } - if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { - logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) - saveSession = true - } - } - if session != nil { - var ok bool - if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil { - logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session) - clearSession = true - session = nil - } else if ok { - saveSession = true - revalidated = true + if session != nil { + if session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { + logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) + saveSession = true + } + + if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil { + logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session) + clearSession = true + session = nil + } else if ok { + saveSession = true + revalidated = true + } } } From 100f126405043fe42ab13f9679da1c94f972708e Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 5 Jun 2019 16:09:29 -0700 Subject: [PATCH 20/37] Make JwtIssuer struct private --- options.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/options.go b/options.go index 511666b..8c73eb9 100644 --- a/options.go +++ b/options.go @@ -171,8 +171,8 @@ func NewOptions() *Options { } } -// JwtIssuer hold parsed JWT issuer info that's used to construct a verifier. -type JwtIssuer struct { +// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier. +type jwtIssuer struct { issuerURI string audience string } @@ -260,7 +260,7 @@ func (o *Options) Validate() error { } // Configure extra issuers if len(o.ExtraJwtIssuers) > 0 { - var jwtIssuers []JwtIssuer + var jwtIssuers []jwtIssuer jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs) for _, jwtIssuer := range jwtIssuers { verifier, err := newVerifierFromJwtIssuer(jwtIssuer) @@ -459,9 +459,9 @@ func parseSignatureKey(o *Options, msgs []string) []string { } // parseJwtIssuers takes in an array of strings in the form of issuer=audience -// and parses to an array of JwtIssuer structs. -func parseJwtIssuers(issuers []string, msgs []string) ([]JwtIssuer, []string) { - var parsedIssuers []JwtIssuer +// and parses to an array of jwtIssuer structs. +func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) { + var parsedIssuers []jwtIssuer for _, jwtVerifier := range issuers { components := strings.Split(jwtVerifier, "=") if len(components) < 2 { @@ -469,14 +469,14 @@ func parseJwtIssuers(issuers []string, msgs []string) ([]JwtIssuer, []string) { continue } uri, audience := components[0], strings.Join(components[1:], "=") - parsedIssuers = append(parsedIssuers, JwtIssuer{issuerURI: uri, audience: audience}) + parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience}) } return parsedIssuers, msgs } -// newVerifierFromJwtIssuer takes in issuer information in JwtIssuer info and returns +// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns // a verifier for that issuer. -func newVerifierFromJwtIssuer(jwtIssuer JwtIssuer) (*oidc.IDTokenVerifier, error) { +func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) { config := &oidc.Config{ ClientID: jwtIssuer.audience, } From 5a50f6223f33d2e68ea4b23e9ffae977d8423bbe Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 17 Jun 2019 12:58:40 -0700 Subject: [PATCH 21/37] Do not infer username from email --- oauthproxy.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index e0f5e48..9afa991 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -917,7 +917,6 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState if claims.Verified != nil && !*claims.Verified { return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) } - user := strings.Split(claims.Email, "@")[0] session = &sessionsapi.SessionState{ AccessToken: rawBearerToken, @@ -925,7 +924,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState RefreshToken: "", ExpiresOn: bearerToken.Expiry, Email: claims.Email, - User: user, + User: claims.Email, } return session, nil } From 058ffd1047e7ec35e9be6288c9f4ef9ac714abb3 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 17 Jun 2019 13:11:49 -0700 Subject: [PATCH 22/37] Update unit tests for username --- oauthproxy_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index decae03..34bf030 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1179,7 +1179,7 @@ func TestGetJwtSession(t *testing.T) { // Bearer session, _ := p.GetJwtSession(req) - assert.Equal(t, session.User, "john") + assert.Equal(t, session.User, "john@example.com") assert.Equal(t, session.Email, "john@example.com") assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) assert.Equal(t, session.IDToken, goodJwt) @@ -1223,12 +1223,12 @@ func TestGetJwtSession(t *testing.T) { // Check PassAuthorization, should overwrite Basic header assert.Equal(t, req.Header.Get("Authorization"), authHeader) - assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john") + assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john@example.com") assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") // SetAuthorization and SetXAuthRequest assert.Equal(t, rw.Header().Get("Authorization"), authHeader) - assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john") + assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john@example.com") assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") } From bd651df3c25c5e82eec093b5c7b5ef1067bbf489 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 20 Jun 2019 13:40:04 -0700 Subject: [PATCH 23/37] Ensure groups in JWT Bearer tokens are also validated Fix a minor auth logging bug --- oauthproxy.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 9afa991..99dfb36 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -650,7 +650,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { } http.Redirect(rw, req, redirect, 302) } else { - logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Invalid authentication via OAuth2: unauthorized") + logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized") p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account") } } @@ -759,11 +759,13 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R } } - if session != nil && session.Email != "" && !p.Validator(session.Email) { - logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session) - session = nil - saveSession = false - clearSession = true + if session != nil && session.Email != "" { + if !p.Validator(session.Email) || !p.provider.ValidateGroup(session.Email) { + logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session) + session = nil + saveSession = false + clearSession = true + } } if saveSession && session != nil { From 3881955605c9edbec15f2a72cb2cbb9ee4a90e2a Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 20 Jun 2019 16:57:20 -0700 Subject: [PATCH 24/37] Update unit tests for ValidateGroup --- oauthproxy_test.go | 137 +++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 48 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 34bf030..35ed59a 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -229,8 +229,9 @@ func TestIsValidRedirect(t *testing.T) { type TestProvider struct { *providers.ProviderData - EmailAddress string - ValidToken bool + EmailAddress string + ValidToken bool + GroupValidator func(string) bool } func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider { @@ -255,6 +256,9 @@ func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider { Scope: "profile.email", }, EmailAddress: emailAddress, + GroupValidator: func(s string) bool { + return true + }, } } @@ -266,6 +270,13 @@ func (tp *TestProvider) ValidateSessionState(session *sessions.SessionState) boo return tp.ValidToken } +func (tp *TestProvider) ValidateGroup(email string) bool { + if tp.GroupValidator != nil { + return tp.GroupValidator(email) + } + return true +} + func TestBasicAuthPassword(t *testing.T) { providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger.Printf("%#v", r) @@ -791,6 +802,25 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { assert.Equal(t, "unauthorized request\n", string(bodyBytes)) } +func TestAuthOnlyEndpointUnauthorizedOnProviderGroupValidationFailure(t *testing.T) { + test := NewAuthOnlyEndpointTest() + startSession := &sessions.SessionState{ + Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()} + test.SaveSession(startSession) + provider := &TestProvider{ + ValidToken: true, + GroupValidator: func(s string) bool { + return false + }, + } + + test.proxy.provider = provider + test.proxy.ServeHTTP(test.rw, test.req) + assert.Equal(t, http.StatusUnauthorized, test.rw.Code) + bodyBytes, _ := ioutil.ReadAll(test.rw.Body) + assert.Equal(t, "unauthorized request\n", string(bodyBytes)) +} + func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) { var pcTest ProcessCookieTest @@ -1168,69 +1198,80 @@ func TestGetJwtSession(t *testing.T) { keyset := NoOpKeySet{} verifier := oidc.NewVerifier("https://issuer.example.com", keyset, &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) - p := OAuthProxy{} - p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) - req, _ := http.NewRequest("GET", "/", strings.NewReader("")) + test := NewAuthOnlyEndpointTest(func(opts *Options) { + opts.PassAuthorization = true + opts.SetAuthorization = true + opts.SetXAuthRequest = true + opts.SkipJwtBearerTokens = true + opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) + }) + tp, _ := test.proxy.provider.(*TestProvider) + tp.GroupValidator = func(s string) bool { + return true + } + authHeader := fmt.Sprintf("Bearer %s", goodJwt) - req.Header = map[string][]string{ + test.req.Header = map[string][]string{ "Authorization": {authHeader}, } // Bearer - session, _ := p.GetJwtSession(req) + session, _ := test.proxy.GetJwtSession(test.req) assert.Equal(t, session.User, "john@example.com") assert.Equal(t, session.Email, "john@example.com") assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) assert.Equal(t, session.IDToken, goodJwt) - jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.Printf("%#v", r) - var payload string - payload = r.Header.Get("Authorization") - if payload == "" { - payload = "No Authorization header found." - } - w.WriteHeader(200) - w.Write([]byte(payload)) - })) - - opts := NewOptions() - opts.Upstreams = append(opts.Upstreams, jwtProviderServer.URL) - opts.PassAuthorization = true - opts.SetAuthorization = true - opts.SetXAuthRequest = true - opts.CookieSecret = "0123456789abcdef0123" - opts.SkipJwtBearerTokens = true - opts.Validate() - - // We can't actually use opts.Validate() because it will attempt to find a jwks URI - opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) - - providerURL, _ := url.Parse(jwtProviderServer.URL) - const emailAddress = "john@example.com" - - opts.provider = NewTestProvider(providerURL, emailAddress) - jwtTestProxy := NewOAuthProxy(opts, func(email string) bool { - return email == emailAddress - }) - - rw := httptest.NewRecorder() - jwtTestProxy.ServeHTTP(rw, req) - if rw.Code >= 400 { - t.Fatalf("expected 3xx got %d", rw.Code) + test.proxy.ServeHTTP(test.rw, test.req) + if test.rw.Code >= 400 { + t.Fatalf("expected 3xx got %d", test.rw.Code) } // Check PassAuthorization, should overwrite Basic header - assert.Equal(t, req.Header.Get("Authorization"), authHeader) - assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john@example.com") - assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") + assert.Equal(t, test.req.Header.Get("Authorization"), authHeader) + assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "john@example.com") + assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com") // SetAuthorization and SetXAuthRequest - assert.Equal(t, rw.Header().Get("Authorization"), authHeader) - assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john@example.com") - assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") + assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader) + assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "john@example.com") + assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com") +} +func TestJwtUnauthorizedOnGroupValidationFailure(t *testing.T) { + goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." + + "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" + + "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" + + "E1LCJleHAiOjE5MTIxNTE4MjF9." + + "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" + + "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8" + + keyset := NoOpKeySet{} + verifier := oidc.NewVerifier("https://issuer.example.com", keyset, + &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) + + test := NewAuthOnlyEndpointTest(func(opts *Options) { + opts.PassAuthorization = true + opts.SetAuthorization = true + opts.SetXAuthRequest = true + opts.SkipJwtBearerTokens = true + opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) + }) + tp, _ := test.proxy.provider.(*TestProvider) + // Verify ValidateGroup fails JWT authorization + tp.GroupValidator = func(s string) bool { + return false + } + + authHeader := fmt.Sprintf("Bearer %s", goodJwt) + test.req.Header = map[string][]string{ + "Authorization": {authHeader}, + } + test.proxy.ServeHTTP(test.rw, test.req) + if test.rw.Code != http.StatusUnauthorized { + t.Fatalf("expected 401 got %d", test.rw.Code) + } } func TestFindJwtBearerToken(t *testing.T) { From 411adf6f21ac6de81a2ade696b53b8d4d012f6a4 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:40:59 +0100 Subject: [PATCH 25/37] Switch linter to golangci-lint --- .golangci.yml | 14 ++++++++++++++ .travis.yml | 3 +-- CHANGELOG.md | 1 + Dockerfile | 1 + Dockerfile.arm64 | 1 + Dockerfile.armv6 | 1 + Makefile | 12 +----------- configure | 4 ++-- 8 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4dc2d94 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,14 @@ +run: + deadline: 120s +linters: + enable: + - govet + # TODO: Not supported by golang-ci - vetshadow + - golint + - ineffassign + - goconst + - deadcode + - gofmt + - goimports + enable-all: false + disable-all: true diff --git a/.travis.yml b/.travis.yml index d1151db..445eb11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,7 @@ install: - wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 - chmod +x dep - mv dep $GOPATH/bin/dep - - go get github.com/alecthomas/gometalinter - - gometalinter --install + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1 script: - ./configure && make test sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 6858187..69d071f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas) - [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess) - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. +- [#XX](https://github.com/pusher/outh2_proxy/pull/XX) Switch from gometalinter to golangci-lint # v3.2.0 diff --git a/Dockerfile b/Dockerfile index f6649a9..757a1f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 RUN chmod +x $GOPATH/bin/dep +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 7f011ba..f4bf495 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 RUN chmod +x $GOPATH/bin/dep +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index e2c57b9..32bb124 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 RUN chmod +x $GOPATH/bin/dep +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Makefile b/Makefile index 720fd4d..3286bda 100644 --- a/Makefile +++ b/Makefile @@ -17,17 +17,7 @@ distclean: clean .PHONY: lint lint: - $(GOMETALINTER) --vendor --disable-all \ - --enable=vet \ - --enable=vetshadow \ - --enable=golint \ - --enable=ineffassign \ - --enable=goconst \ - --enable=deadcode \ - --enable=gofmt \ - --enable=goimports \ - --deadline=120s \ - --tests ./... + $(GOLANGCILINT) run .PHONY: dep dep: diff --git a/configure b/configure index 5d74683..f1b8ae6 100755 --- a/configure +++ b/configure @@ -126,7 +126,7 @@ check_for go check_go_version check_go_env check_for dep -check_for gometalinter +check_for golangci-lint echo @@ -135,7 +135,7 @@ cat <<- EOF > .env GO := "${tools[go]}" GO_VERSION := ${tools[go_version]} DEP := "${tools[dep]}" - GOMETALINTER := "${tools[gometalinter]}" + GOLANGCILINT := "${tools[golangci-lint]}" EOF echo "Environment configuration written to .env" From d24aacdb5c34630a7a8ca3b2f9fe0ef1c093a37d Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:41:23 +0100 Subject: [PATCH 26/37] Fix lint errors --- http_test.go | 27 ++++++++++++++----------- oauthproxy.go | 17 +++++++++++++--- oauthproxy_test.go | 50 +++++++++++++++++++++++----------------------- options.go | 2 +- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/http_test.go b/http_test.go index b365d6f..f5ee142 100644 --- a/http_test.go +++ b/http_test.go @@ -8,6 +8,9 @@ import ( "github.com/stretchr/testify/assert" ) +const localhost = "127.0.0.1" +const host = "test-server" + func TestGCPHealthcheckLiveness(t *testing.T) { handler := func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("test")) @@ -16,8 +19,8 @@ func TestGCPHealthcheckLiveness(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/liveness_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, 200, rw.Code) @@ -32,8 +35,8 @@ func TestGCPHealthcheckReadiness(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/readiness_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, 200, rw.Code) @@ -48,8 +51,8 @@ func TestGCPHealthcheckNotHealthcheck(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/not_any_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, "test", rw.Body.String()) @@ -63,8 +66,8 @@ func TestGCPHealthcheckIngress(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) @@ -80,8 +83,8 @@ func TestGCPHealthcheckNotIngress(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/foo", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) @@ -96,8 +99,8 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("PUT", "/", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) diff --git a/oauthproxy.go b/oauthproxy.go index 99dfb36..da56cfc 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -160,7 +160,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) { } // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url -func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) { +func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler { u.Path = "" proxy := NewReverseProxy(u, opts.FlushInterval) if !opts.PassHostHeader { @@ -176,7 +176,12 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma wsURL := &url.URL{Scheme: wsScheme, Host: u.Host} wsProxy = wsutil.NewSingleHostReverseProxy(wsURL) } - return &UpstreamProxy{u.Host, proxy, wsProxy, auth} + return &UpstreamProxy{ + upstream: u.Host, + handler: proxy, + wsHandler: wsProxy, + auth: auth, + } } // NewOAuthProxy creates a new instance of OOuthProxy from the options provided @@ -201,7 +206,13 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { } logger.Printf("mapping path %q => file system %q", path, u.Path) proxy := NewFileServer(path, u.Path) - serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil}) + uProxy := UpstreamProxy{ + upstream: path, + handler: proxy, + wsHandler: nil, + auth: nil, + } + serveMux.Handle(path, &uProxy) default: panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme)) } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 35ed59a..f114c7e 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -163,9 +163,9 @@ func TestEncodedSlashes(t *testing.T) { func TestRobotsTxt(t *testing.T) { opts := NewOptions() - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "asdlkjx" + opts.ClientSecret = "alkgks" + opts.CookieSecret = "asdkugkj" opts.Validate() proxy := NewOAuthProxy(opts, func(string) bool { return true }) @@ -178,9 +178,9 @@ func TestRobotsTxt(t *testing.T) { func TestIsValidRedirect(t *testing.T) { opts := NewOptions() - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "skdlfj" + opts.ClientSecret = "fgkdsgj" + opts.CookieSecret = "ljgiogbj" // Should match domains that are exactly foo.bar and any subdomain of bar.foo opts.WhitelistDomains = []string{"foo.bar", ".bar.foo"} opts.Validate() @@ -298,8 +298,8 @@ func TestBasicAuthPassword(t *testing.T) { // The CookieSecret must be 32 bytes in order to create the AES // cipher. opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" + opts.ClientID = "dlgkj" + opts.ClientSecret = "alkgret" opts.CookieSecure = false opts.PassBasicAuth = true opts.PassUserHeaders = true @@ -392,8 +392,8 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes // The CookieSecret must be 32 bytes in order to create the AES // cipher. t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" - t.opts.ClientID = "bazquux" - t.opts.ClientSecret = "foobar" + t.opts.ClientID = "slgkj" + t.opts.ClientSecret = "gfjgojl" t.opts.CookieSecure = false t.opts.PassAccessToken = opts.PassAccessToken t.opts.Validate() @@ -518,9 +518,9 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest { var sipTest SignInPageTest sipTest.opts = NewOptions() - sipTest.opts.CookieSecret = "foobar" - sipTest.opts.ClientID = "bazquux" - sipTest.opts.ClientSecret = "xyzzyplugh" + sipTest.opts.CookieSecret = "adklsj2" + sipTest.opts.ClientID = "lkdgj" + sipTest.opts.ClientSecret = "sgiufgoi" sipTest.opts.SkipProviderButton = skipProvider sipTest.opts.Validate() @@ -624,8 +624,8 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi for _, modifier := range modifiers { modifier(pcTest.opts) } - pcTest.opts.ClientID = "bazquux" - pcTest.opts.ClientSecret = "xyzzyplugh" + pcTest.opts.ClientID = "asdfljk" + pcTest.opts.ClientSecret = "lkjfdsig" pcTest.opts.CookieSecret = "0123456789abcdefabcd" // First, set the CookieRefresh option so proxy.AesCipher is created, // needed to encrypt the access_token. @@ -860,9 +860,9 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) { opts := NewOptions() opts.Upstreams = append(opts.Upstreams, upstream.URL) - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "aljsal" + opts.ClientSecret = "jglkfsdgj" + opts.CookieSecret = "dkfjgdls" opts.SkipAuthPreflight = true opts.Validate() @@ -999,8 +999,8 @@ func TestNoRequestSignature(t *testing.T) { func TestRequestSignatureGetRequest(t *testing.T) { st := NewSignatureTest() defer st.Close() - st.opts.SignatureKey = "sha1:foobar" - st.MakeRequestWithExpectedKey("GET", "", "foobar") + st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d" + st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d") assert.Equal(t, 200, st.rw.Code) assert.Equal(t, st.rw.Body.String(), "signatures match") } @@ -1008,9 +1008,9 @@ func TestRequestSignatureGetRequest(t *testing.T) { func TestRequestSignaturePostRequest(t *testing.T) { st := NewSignatureTest() defer st.Close() - st.opts.SignatureKey = "sha1:foobar" + st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61" payload := `{ "hello": "world!" }` - st.MakeRequestWithExpectedKey("POST", payload, "foobar") + st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61") assert.Equal(t, 200, st.rw.Code) assert.Equal(t, st.rw.Body.String(), "signatures match") } @@ -1056,9 +1056,9 @@ type ajaxRequestTest struct { func newAjaxRequestTest() *ajaxRequestTest { test := &ajaxRequestTest{} test.opts = NewOptions() - test.opts.CookieSecret = "foobar" - test.opts.ClientID = "bazquux" - test.opts.ClientSecret = "xyzzyplugh" + test.opts.CookieSecret = "sdflsw" + test.opts.ClientID = "gkljfdl" + test.opts.ClientSecret = "sdflkjs" test.opts.Validate() test.proxy = NewOAuthProxy(test.opts, func(email string) bool { return true diff --git a/options.go b/options.go index 8c73eb9..1f438b5 100644 --- a/options.go +++ b/options.go @@ -454,7 +454,7 @@ func parseSignatureKey(o *Options, msgs []string) []string { return append(msgs, "unsupported signature hash algorithm: "+ o.SignatureKey) } - o.signatureData = &SignatureData{hash, secretKey} + o.signatureData = &SignatureData{hash: hash, key: secretKey} return msgs } From 5bcb998e6be341857d2c845a837a3c3c79dc63af Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:55:42 +0100 Subject: [PATCH 27/37] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d071f..816fa97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,7 @@ - [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas) - [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess) - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. -- [#XX](https://github.com/pusher/outh2_proxy/pull/XX) Switch from gometalinter to golangci-lint +- [#198](https://github.com/pusher/outh2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore) # v3.2.0 From 924eab6355ae4414407a83583ba45cc29d8054db Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Wed, 19 Jun 2019 15:24:25 +0100 Subject: [PATCH 28/37] Adds banner flag This is to override what's displayed on the main page. --- docs/configuration/configuration.md | 1 + main.go | 9 ++++++++- oauthproxy.go | 2 ++ options.go | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 1295269..5232d4e 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -43,6 +43,7 @@ Usage of oauth2_proxy: -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email -extra-jwt-issuers: if -skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json) -flush-interval: period between flushing response buffers when streaming responses (default "1s") + -banner string: custom banner string. Use "-" to disable default banner. -footer string: custom footer string. Use "-" to disable default footer. -gcp-healthchecks: will enable /liveness_check, /readiness_check, and / (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses (default false) -github-org string: restrict logins to members of this organisation diff --git a/main.go b/main.go index 054bb30..6884462 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,7 @@ func main() { flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption") flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided") flagSet.String("custom-templates-dir", "", "path to custom html templates") + flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.") flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.") flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. //sign_in)") flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying") @@ -148,7 +149,13 @@ func main() { validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile) oauthproxy := NewOAuthProxy(opts, validator) - if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" { + if len(opts.Banner) >= 1 { + if opts.Banner == "-" { + oauthproxy.SignInMessage = "" + } else { + oauthproxy.SignInMessage = opts.Banner + } + } else if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" { if len(opts.EmailDomains) > 1 { oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(opts.EmailDomains, ", ")) } else if opts.EmailDomains[0] != "*" { diff --git a/oauthproxy.go b/oauthproxy.go index 99dfb36..08dcfab 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -98,6 +98,7 @@ type OAuthProxy struct { jwtBearerVerifiers []*oidc.IDTokenVerifier compiledRegex []*regexp.Regexp templates *template.Template + Banner string Footer string } @@ -269,6 +270,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { PassAuthorization: opts.PassAuthorization, SkipProviderButton: opts.SkipProviderButton, templates: loadTemplates(opts.CustomTemplatesDir), + Banner: opts.Banner, Footer: opts.Footer, } } diff --git a/options.go b/options.go index 8c73eb9..a8de2d7 100644 --- a/options.go +++ b/options.go @@ -51,6 +51,7 @@ type Options struct { HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"` DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"` CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"` + Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"` Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"` // Embed CookieOptions From b9cfa8f49f82aeb6bdae879957aea3aa236b400b Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Wed, 19 Jun 2019 15:35:32 +0100 Subject: [PATCH 29/37] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6858187..37092f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas) - [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess) - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. +- [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) # v3.2.0 From ce7e3840958fe3e2e62eab2386f25dd6e20f13b9 Mon Sep 17 00:00:00 2001 From: hjenkins Date: Mon, 1 Jul 2019 16:27:19 +0100 Subject: [PATCH 30/37] Remove TODO vetshadow as it's part of govet --- .golangci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 4dc2d94..a0658e1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,6 @@ run: linters: enable: - govet - # TODO: Not supported by golang-ci - vetshadow - golint - ineffassign - goconst From 387a7267e124d5bcee37f551fe1491b96c4fc98c Mon Sep 17 00:00:00 2001 From: "Seip, Nikolai" Date: Wed, 10 Jul 2019 10:26:31 +0200 Subject: [PATCH 31/37] update configuration.md auth_request section --- docs/configuration/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5232d4e..b3c38b2 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -313,3 +313,5 @@ nginx.ingress.kubernetes.io/configuration-snippet: | end } ``` + +You have to substitute *name* with the actual cookie name you configured via --cookie-name parameter. If you don't set a custom cookie name the variable should be "$upstream_cookie__oauth2_proxy_1" instead of "$upstream_cookie_name_1" and the new cookie-name should be "_oauth2_proxy_1=" instead of "name_1=". From 018a25be04dfcab9fae3ee12ea6710bc52d578ad Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Mon, 20 May 2019 12:32:10 +0100 Subject: [PATCH 32/37] Create option to skip verified email check in OIDC provider --- docs/configuration/configuration.md | 1 + main.go | 1 + options.go | 69 +++++++++++++++-------------- providers/oidc.go | 5 ++- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index b3c38b2..d7cdcd9 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -63,6 +63,7 @@ Usage of oauth2_proxy: -jwt-key string: private key in PEM format used to sign JWT, so that you can say something like -jwt-key="${OAUTH2_PROXY_JWT_KEY}": required by login.gov -jwt-key-file string: path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov -login-url string: Authentication endpoint + -oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com" -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled -pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header diff --git a/main.go b/main.go index 6884462..4187257 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ func main() { flagSet.String("provider", "google", "OAuth provider") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") + flagSet.Bool("oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") flagSet.String("login-url", "", "Authentication endpoint") diff --git a/options.go b/options.go index 4a41ebe..3cd6e3a 100644 --- a/options.go +++ b/options.go @@ -79,17 +79,18 @@ type Options struct { // These options allow for other providers besides Google, with // potential overrides. - Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"` - OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"` - SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` - OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"` - LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"` - RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"` - ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` - ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` - ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` - Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` - ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` + Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"` + OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"` + OIDCAllowUnverifiedEmail bool `flag:"oidc-allow-unverified-email" cfg:"oidc_allow_unverified_email" env:"OAUTH2_PROXY_OIDC_ALLOW_UNVERIFIED_EMAIL"` + SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` + OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"` + LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"` + RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"` + ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` + ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` + ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` + Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` + ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Configuration values for logging LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"` @@ -147,28 +148,29 @@ func NewOptions() *Options { SessionOptions: options.SessionOptions{ Type: "cookie", }, - SetXAuthRequest: false, - SkipAuthPreflight: false, - PassBasicAuth: true, - PassUserHeaders: true, - PassAccessToken: false, - PassHostHeader: true, - SetAuthorization: false, - PassAuthorization: false, - ApprovalPrompt: "force", - SkipOIDCDiscovery: false, - LoggingFilename: "", - LoggingMaxSize: 100, - LoggingMaxAge: 7, - LoggingMaxBackups: 0, - LoggingLocalTime: true, - LoggingCompress: false, - StandardLogging: true, - StandardLoggingFormat: logger.DefaultStandardLoggingFormat, - RequestLogging: true, - RequestLoggingFormat: logger.DefaultRequestLoggingFormat, - AuthLogging: true, - AuthLoggingFormat: logger.DefaultAuthLoggingFormat, + SetXAuthRequest: false, + SkipAuthPreflight: false, + PassBasicAuth: true, + PassUserHeaders: true, + PassAccessToken: false, + PassHostHeader: true, + SetAuthorization: false, + PassAuthorization: false, + ApprovalPrompt: "force", + OIDCAllowUnverifiedEmail: false, + SkipOIDCDiscovery: false, + LoggingFilename: "", + LoggingMaxSize: 100, + LoggingMaxAge: 7, + LoggingMaxBackups: 0, + LoggingLocalTime: true, + LoggingCompress: false, + StandardLogging: true, + StandardLoggingFormat: logger.DefaultStandardLoggingFormat, + RequestLogging: true, + RequestLoggingFormat: logger.DefaultRequestLoggingFormat, + AuthLogging: true, + AuthLoggingFormat: logger.DefaultAuthLoggingFormat, } } @@ -397,6 +399,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { } } case *providers.OIDCProvider: + p.AllowUnverifiedEmail = o.OIDCAllowUnverifiedEmail if o.oidcVerifier == nil { msgs = append(msgs, "oidc provider requires an oidc issuer URL") } else { diff --git a/providers/oidc.go b/providers/oidc.go index b0d2dda..86a58f6 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -14,7 +14,8 @@ import ( type OIDCProvider struct { *ProviderData - Verifier *oidc.IDTokenVerifier + Verifier *oidc.IDTokenVerifier + AllowUnverifiedEmail bool } // NewOIDCProvider initiates a new OIDCProvider @@ -119,7 +120,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok // TODO: Try getting email from /userinfo before falling back to Subject claims.Email = claims.Subject } - if claims.Verified != nil && !*claims.Verified { + if !p.AllowUnverifiedEmail && claims.Verified != nil && !*claims.Verified { return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) } From 39b6a42d43b96cf41ee110406c4dd1b7ffc565b7 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:18:36 +0100 Subject: [PATCH 33/37] Mark option to skip verified email check as insecure --- options.go | 72 +++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/options.go b/options.go index 3cd6e3a..7436d85 100644 --- a/options.go +++ b/options.go @@ -79,18 +79,18 @@ type Options struct { // These options allow for other providers besides Google, with // potential overrides. - Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"` - OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"` - OIDCAllowUnverifiedEmail bool `flag:"oidc-allow-unverified-email" cfg:"oidc_allow_unverified_email" env:"OAUTH2_PROXY_OIDC_ALLOW_UNVERIFIED_EMAIL"` - SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` - OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"` - LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"` - RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"` - ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` - ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` - ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` - Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` - ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` + Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"` + OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"` + InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"` + SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` + OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"` + LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"` + RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"` + ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` + ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` + ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` + Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` + ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Configuration values for logging LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"` @@ -148,29 +148,29 @@ func NewOptions() *Options { SessionOptions: options.SessionOptions{ Type: "cookie", }, - SetXAuthRequest: false, - SkipAuthPreflight: false, - PassBasicAuth: true, - PassUserHeaders: true, - PassAccessToken: false, - PassHostHeader: true, - SetAuthorization: false, - PassAuthorization: false, - ApprovalPrompt: "force", - OIDCAllowUnverifiedEmail: false, - SkipOIDCDiscovery: false, - LoggingFilename: "", - LoggingMaxSize: 100, - LoggingMaxAge: 7, - LoggingMaxBackups: 0, - LoggingLocalTime: true, - LoggingCompress: false, - StandardLogging: true, - StandardLoggingFormat: logger.DefaultStandardLoggingFormat, - RequestLogging: true, - RequestLoggingFormat: logger.DefaultRequestLoggingFormat, - AuthLogging: true, - AuthLoggingFormat: logger.DefaultAuthLoggingFormat, + SetXAuthRequest: false, + SkipAuthPreflight: false, + PassBasicAuth: true, + PassUserHeaders: true, + PassAccessToken: false, + PassHostHeader: true, + SetAuthorization: false, + PassAuthorization: false, + ApprovalPrompt: "force", + InsecureOIDCAllowUnverifiedEmail: false, + SkipOIDCDiscovery: false, + LoggingFilename: "", + LoggingMaxSize: 100, + LoggingMaxAge: 7, + LoggingMaxBackups: 0, + LoggingLocalTime: true, + LoggingCompress: false, + StandardLogging: true, + StandardLoggingFormat: logger.DefaultStandardLoggingFormat, + RequestLogging: true, + RequestLoggingFormat: logger.DefaultRequestLoggingFormat, + AuthLogging: true, + AuthLoggingFormat: logger.DefaultAuthLoggingFormat, } } @@ -399,7 +399,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { } } case *providers.OIDCProvider: - p.AllowUnverifiedEmail = o.OIDCAllowUnverifiedEmail + p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail if o.oidcVerifier == nil { msgs = append(msgs, "oidc provider requires an oidc issuer URL") } else { From 776d063b987f950bb238680cc6e52174dbd4a7e4 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:23:24 +0100 Subject: [PATCH 34/37] Update changelog to include --insecure-oidc-allow-unverified-email --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e2212..ed9d4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. - [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) - [#198](https://github.com/pusher/outh2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore) +- [#159](https://github.com/pusher/oauth2_proxy/pull/159) Add option to skip the OIDC provider verified email check: `--insecure-oidc-allow-unverified-email` # v3.2.0 From 9823971b7d9e7e5f24a988b488c8a0955dbafaa8 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:58:31 +0100 Subject: [PATCH 35/37] Make insecure-oidc-allow-unverified-email configuration usage consistent --- docs/configuration/configuration.md | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d7cdcd9..016fe3a 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -63,7 +63,7 @@ Usage of oauth2_proxy: -jwt-key string: private key in PEM format used to sign JWT, so that you can say something like -jwt-key="${OAUTH2_PROXY_JWT_KEY}": required by login.gov -jwt-key-file string: path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov -login-url string: Authentication endpoint - -oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified + -insecure-oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com" -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled -pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header diff --git a/main.go b/main.go index 4187257..f82205d 100644 --- a/main.go +++ b/main.go @@ -104,7 +104,7 @@ func main() { flagSet.String("provider", "google", "OAuth provider") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") - flagSet.Bool("oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") + flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") flagSet.String("login-url", "", "Authentication endpoint") From 27bdb194b1ce7ad7e79ae47343d5c7d2794cfb31 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sat, 13 Jul 2019 22:14:05 +0100 Subject: [PATCH 36/37] Update to Alpine 3.10 --- Dockerfile | 2 +- Dockerfile.arm64 | 2 +- Dockerfile.armv6 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 757a1f5..53fca88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM alpine:3.9 +FROM alpine:3.10 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index f4bf495..1db9050 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && GOARCH=arm64 make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM arm64v8/alpine:3.9 +FROM arm64v8/alpine:3.10 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 32bb124..40cc5f5 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && GOARCH=arm GOARM=6 make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM arm32v6/alpine:3.9 +FROM arm32v6/alpine:3.10 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem From e92e2f0cb45ad34fd1c81bc84adec76c0c61ac18 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 14 Jul 2019 13:32:37 +0100 Subject: [PATCH 37/37] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9d4df..839a720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) - [#198](https://github.com/pusher/outh2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore) - [#159](https://github.com/pusher/oauth2_proxy/pull/159) Add option to skip the OIDC provider verified email check: `--insecure-oidc-allow-unverified-email` +- [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore) # v3.2.0