From e9f787957e72f110277728dc1a02c33215a4a317 Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Fri, 23 Oct 2020 22:06:50 -0700 Subject: [PATCH 1/6] Standardize provider interface method names --- oauthproxy.go | 6 +++--- oauthproxy_test.go | 2 +- providers/digitalocean.go | 2 +- providers/facebook.go | 2 +- providers/github.go | 4 ++-- providers/gitlab.go | 4 ++-- providers/gitlab_test.go | 12 ++++++------ providers/google.go | 2 +- providers/internal_util_test.go | 2 +- providers/linkedin.go | 2 +- providers/oidc.go | 4 ++-- providers/oidc_test.go | 2 +- providers/provider_default.go | 6 +++--- providers/provider_default_test.go | 2 +- providers/providers.go | 6 +++--- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 28df21f4..02891612 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -270,7 +270,7 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt if opts.GetOIDCVerifier() != nil { sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{ Verifier: opts.GetOIDCVerifier(), - TokenToSession: opts.GetProvider().CreateSessionStateFromBearerToken, + TokenToSession: opts.GetProvider().CreateSessionFromBearer, }) } @@ -291,7 +291,7 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt SessionStore: sessionStore, RefreshPeriod: opts.Cookie.Refresh, RefreshSessionIfNeeded: opts.GetProvider().RefreshSessionIfNeeded, - ValidateSessionState: opts.GetProvider().ValidateSessionState, + ValidateSessionState: opts.GetProvider().ValidateSession, })) return chain @@ -416,7 +416,7 @@ func (p *OAuthProxy) enrichSessionState(ctx context.Context, s *sessionsapi.Sess } } - return p.provider.EnrichSessionState(ctx, s) + return p.provider.EnrichSession(ctx, s) } // MakeCSRFCookie creates a cookie for CSRF diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 6ed4b30c..fe68c90e 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -400,7 +400,7 @@ func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionSt return tp.EmailAddress, nil } -func (tp *TestProvider) ValidateSessionState(_ context.Context, _ *sessions.SessionState) bool { +func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool { return tp.ValidToken } diff --git a/providers/digitalocean.go b/providers/digitalocean.go index 94b2ea90..b5bd4be4 100644 --- a/providers/digitalocean.go +++ b/providers/digitalocean.go @@ -83,6 +83,6 @@ func (p *DigitalOceanProvider) GetEmailAddress(ctx context.Context, s *sessions. } // ValidateSessionState validates the AccessToken -func (p *DigitalOceanProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *DigitalOceanProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken)) } diff --git a/providers/facebook.go b/providers/facebook.go index d2ae132d..e3babc0d 100644 --- a/providers/facebook.go +++ b/providers/facebook.go @@ -89,6 +89,6 @@ func (p *FacebookProvider) GetEmailAddress(ctx context.Context, s *sessions.Sess } // ValidateSessionState validates the AccessToken -func (p *FacebookProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *FacebookProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken)) } diff --git a/providers/github.go b/providers/github.go index d1be571f..7d029ffc 100644 --- a/providers/github.go +++ b/providers/github.go @@ -103,7 +103,7 @@ func (p *GitHubProvider) SetUsers(users []string) { } // EnrichSessionState updates the User & Email after the initial Redeem -func (p *GitHubProvider) EnrichSessionState(ctx context.Context, s *sessions.SessionState) error { +func (p *GitHubProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { err := p.getEmail(ctx, s) if err != nil { return err @@ -112,7 +112,7 @@ func (p *GitHubProvider) EnrichSessionState(ctx context.Context, s *sessions.Ses } // ValidateSessionState validates the AccessToken -func (p *GitHubProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *GitHubProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return validateToken(ctx, p, s.AccessToken, makeGitHubHeader(s.AccessToken)) } diff --git a/providers/gitlab.go b/providers/gitlab.go index a04beca6..bb02f4df 100644 --- a/providers/gitlab.go +++ b/providers/gitlab.go @@ -188,13 +188,13 @@ func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.T } // ValidateSessionState checks that the session's IDToken is still valid -func (p *GitLabProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *GitLabProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { _, err := p.Verifier.Verify(ctx, s.IDToken) return err == nil } // GetEmailAddress returns the Account email address -func (p *GitLabProvider) EnrichSessionState(ctx context.Context, s *sessions.SessionState) error { +func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { // Retrieve user info userInfo, err := p.getUserInfo(ctx, s) if err != nil { diff --git a/providers/gitlab_test.go b/providers/gitlab_test.go index 12b9d6f4..e3d974bf 100644 --- a/providers/gitlab_test.go +++ b/providers/gitlab_test.go @@ -64,7 +64,7 @@ func TestGitLabProviderBadToken(t *testing.T) { p := testGitLabProvider(bURL.Host) session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.Error(t, err) } @@ -76,7 +76,7 @@ func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) { p := testGitLabProvider(bURL.Host) session := &sessions.SessionState{AccessToken: "gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.Error(t, err) } @@ -89,7 +89,7 @@ func TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) { p.AllowUnverifiedEmail = true session := &sessions.SessionState{AccessToken: "gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.NoError(t, err) assert.Equal(t, "foo@bar.com", session.Email) } @@ -103,7 +103,7 @@ func TestGitLabProviderUsername(t *testing.T) { p.AllowUnverifiedEmail = true session := &sessions.SessionState{AccessToken: "gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.NoError(t, err) assert.Equal(t, "FooBar", session.User) } @@ -118,7 +118,7 @@ func TestGitLabProviderGroupMembershipValid(t *testing.T) { p.Groups = []string{"foo"} session := &sessions.SessionState{AccessToken: "gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.NoError(t, err) assert.Equal(t, "FooBar", session.User) } @@ -133,6 +133,6 @@ func TestGitLabProviderGroupMembershipMissing(t *testing.T) { p.Groups = []string{"baz"} session := &sessions.SessionState{AccessToken: "gitlab_access_token"} - err := p.EnrichSessionState(context.Background(), session) + err := p.EnrichSession(context.Background(), session) assert.Error(t, err) } diff --git a/providers/google.go b/providers/google.go index 36e84885..a05410e7 100644 --- a/providers/google.go +++ b/providers/google.go @@ -179,7 +179,7 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code string) ( // EnrichSessionState checks the listed Google Groups configured and adds any // that the user is a member of to session.Groups. -func (p *GoogleProvider) EnrichSessionState(ctx context.Context, s *sessions.SessionState) error { +func (p *GoogleProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { // TODO (@NickMeves) - Move to pure EnrichSessionState logic and stop // reusing legacy `groupValidator`. // diff --git a/providers/internal_util_test.go b/providers/internal_util_test.go index 991243a1..6c2a1b88 100644 --- a/providers/internal_util_test.go +++ b/providers/internal_util_test.go @@ -32,7 +32,7 @@ func (tp *ValidateSessionStateTestProvider) GetEmailAddress(ctx context.Context, // Note that we're testing the internal validateToken() used to implement // several Provider's ValidateSessionState() implementations -func (tp *ValidateSessionStateTestProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (tp *ValidateSessionStateTestProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return false } diff --git a/providers/linkedin.go b/providers/linkedin.go index 4a45cfe0..58217952 100644 --- a/providers/linkedin.go +++ b/providers/linkedin.go @@ -94,6 +94,6 @@ func (p *LinkedInProvider) GetEmailAddress(ctx context.Context, s *sessions.Sess } // ValidateSessionState validates the AccessToken -func (p *LinkedInProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *LinkedInProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return validateToken(ctx, p, s.AccessToken, makeLinkedInHeader(s.AccessToken)) } diff --git a/providers/oidc.go b/providers/oidc.go index 0f9fc28a..7c48c42a 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -175,7 +175,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok return newSession, nil } -func (p *OIDCProvider) CreateSessionStateFromBearerToken(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) { +func (p *OIDCProvider) CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) { newSession, err := p.createSessionStateInternal(ctx, idToken, nil) if err != nil { return nil, err @@ -221,7 +221,7 @@ func (p *OIDCProvider) createSessionStateInternal(ctx context.Context, idToken * } // ValidateSessionState checks that the session's IDToken is still valid -func (p *OIDCProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { _, err := p.Verifier.Verify(ctx, s.IDToken) return err == nil } diff --git a/providers/oidc_test.go b/providers/oidc_test.go index 2293428b..cc4cdc8a 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -354,7 +354,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) { idToken, err := verifier.Verify(context.Background(), rawIDToken) assert.NoError(t, err) - ss, err := provider.CreateSessionStateFromBearerToken(context.Background(), rawIDToken, idToken) + ss, err := provider.CreateSessionFromBearer(context.Background(), rawIDToken, idToken) assert.NoError(t, err) assert.Equal(t, tc.ExpectedUser, ss.User) diff --git a/providers/provider_default.go b/providers/provider_default.go index 00b70641..7a8c4e40 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -94,7 +94,7 @@ func (p *ProviderData) GetEmailAddress(_ context.Context, _ *sessions.SessionSta // EnrichSessionState is called after Redeem to allow providers to enrich session fields // such as User, Email, Groups with provider specific API calls. -func (p *ProviderData) EnrichSessionState(_ context.Context, _ *sessions.SessionState) error { +func (p *ProviderData) EnrichSession(_ context.Context, _ *sessions.SessionState) error { return nil } @@ -115,7 +115,7 @@ func (p *ProviderData) Authorize(_ context.Context, s *sessions.SessionState) (b } // ValidateSessionState validates the AccessToken -func (p *ProviderData) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool { +func (p *ProviderData) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { return validateToken(ctx, p, s.AccessToken, nil) } @@ -127,6 +127,6 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses // CreateSessionStateFromBearerToken should be implemented to allow providers // to convert ID tokens into sessions -func (p *ProviderData) CreateSessionStateFromBearerToken(_ context.Context, _ string, _ *oidc.IDToken) (*sessions.SessionState, error) { +func (p *ProviderData) CreateSessionFromBearer(_ context.Context, _ string, _ *oidc.IDToken) (*sessions.SessionState, error) { return nil, ErrNotImplemented } diff --git a/providers/provider_default_test.go b/providers/provider_default_test.go index c9e87b33..5f02ecbb 100644 --- a/providers/provider_default_test.go +++ b/providers/provider_default_test.go @@ -52,7 +52,7 @@ func TestAcrValuesConfigured(t *testing.T) { func TestEnrichSessionState(t *testing.T) { p := &ProviderData{} s := &sessions.SessionState{} - assert.NoError(t, p.EnrichSessionState(context.Background(), s)) + assert.NoError(t, p.EnrichSession(context.Background(), s)) } func TestProviderDataAuthorize(t *testing.T) { diff --git a/providers/providers.go b/providers/providers.go index 50f4d6b2..09abf725 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -13,12 +13,12 @@ type Provider interface { // DEPRECATED: Migrate to EnrichSessionState GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) Redeem(ctx context.Context, redirectURI, code string) (*sessions.SessionState, error) - EnrichSessionState(ctx context.Context, s *sessions.SessionState) error + EnrichSession(ctx context.Context, s *sessions.SessionState) error Authorize(ctx context.Context, s *sessions.SessionState) (bool, error) - ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool + ValidateSession(ctx context.Context, s *sessions.SessionState) bool GetLoginURL(redirectURI, finalRedirect string) string RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) - CreateSessionStateFromBearerToken(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) + CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) } // New provides a new Provider based on the configured provider string From 3e9717d489b1ce527ac48379584594e58ca9dce6 Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Fri, 23 Oct 2020 23:34:06 -0700 Subject: [PATCH 2/6] Decouple TokenToSession from OIDC & add a generic VerifyFunc --- oauthproxy.go | 10 +++-- pkg/apis/middleware/session.go | 12 +++--- pkg/middleware/jwt_session.go | 34 +++++++++++------ pkg/middleware/jwt_session_test.go | 59 +++++++++++++++++++----------- providers/oidc.go | 17 +++++++-- providers/oidc_test.go | 16 +++++--- providers/provider_default.go | 5 +-- providers/providers.go | 4 +- 8 files changed, 102 insertions(+), 55 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 02891612..d546f005 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -269,14 +269,18 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt sessionLoaders := []middlewareapi.TokenToSessionLoader{} if opts.GetOIDCVerifier() != nil { sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{ - Verifier: opts.GetOIDCVerifier(), - TokenToSession: opts.GetProvider().CreateSessionFromBearer, + Verifier: func(ctx context.Context, token string) (interface{}, error) { + return opts.GetOIDCVerifier().Verify(ctx, token) + }, + TokenToSession: opts.GetProvider().CreateSessionFromToken, }) } for _, verifier := range opts.GetJWTBearerVerifiers() { sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{ - Verifier: verifier, + Verifier: func(ctx context.Context, token string) (interface{}, error) { + return verifier.Verify(ctx, token) + }, }) } diff --git a/pkg/apis/middleware/session.go b/pkg/apis/middleware/session.go index 95a76fba..a8a3bbea 100644 --- a/pkg/apis/middleware/session.go +++ b/pkg/apis/middleware/session.go @@ -3,22 +3,24 @@ package middleware import ( "context" - "github.com/coreos/go-oidc" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" ) // TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a // SessionState. -type TokenToSessionFunc func(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessionsapi.SessionState, error) +type TokenToSessionFunc func(ctx context.Context, token string, verify VerifyFunc) (*sessionsapi.SessionState, error) + +// VerifyFunc takes a raw bearer token and verifies it +type VerifyFunc func(ctx context.Context, token string) (interface{}, error) // TokenToSessionLoader pairs a token verifier with the correct converter function // to convert the ID Token to a SessionState. type TokenToSessionLoader struct { - // Verfier is used to verify that the ID Token was signed by the claimed issuer + // Verifier is used to verify that the ID Token was signed by the claimed issuer // and that the token has not been tampered with. - Verifier *oidc.IDTokenVerifier + Verifier VerifyFunc - // TokenToSession converts a rawIDToken and an idToken to a SessionState. + // TokenToSession converts a raw bearer token to a SessionState. // (Optional) If not set a default basic implementation is used. TokenToSession TokenToSessionFunc } diff --git a/pkg/middleware/jwt_session.go b/pkg/middleware/jwt_session.go index 024a45ac..5e99e0df 100644 --- a/pkg/middleware/jwt_session.go +++ b/pkg/middleware/jwt_session.go @@ -13,14 +13,14 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" ) -const jwtRegexFormat = `^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` +const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) alice.Constructor { for i, loader := range sessionLoaders { if loader.TokenToSession == nil { sessionLoaders[i] = middlewareapi.TokenToSessionLoader{ Verifier: loader.Verifier, - TokenToSession: createSessionStateFromBearerToken, + TokenToSession: createSessionFromToken, } } } @@ -75,24 +75,24 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio return nil, nil } - rawBearerToken, err := j.findBearerTokenFromHeader(auth) + token, err := j.findTokenFromHeader(auth) if err != nil { return nil, err } for _, loader := range j.sessionLoaders { - bearerToken, err := loader.Verifier.Verify(req.Context(), rawBearerToken) + session, err := loader.TokenToSession(req.Context(), token, loader.Verifier) if err == nil { - // The token was verified, convert it to a session - return loader.TokenToSession(req.Context(), rawBearerToken, bearerToken) + return session, nil } } + // TODO (@NickMeves) Aggregate error logs in the chain return nil, fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization")) } -// findBearerTokenFromHeader finds a valid JWT token from the Authorization header of a given request. -func (j *jwtSessionLoader) findBearerTokenFromHeader(header string) (string, error) { +// findTokenFromHeader finds a valid JWT token from the Authorization header of a given request. +func (j *jwtSessionLoader) findTokenFromHeader(header string) (string, error) { tokenType, token, err := splitAuthHeader(header) if err != nil { return "", err @@ -133,9 +133,9 @@ func (j *jwtSessionLoader) getBasicToken(token string) (string, error) { return "", fmt.Errorf("invalid basic auth token found in authorization header") } -// createSessionStateFromBearerToken is a default implementation for converting +// createSessionFromToken is a default implementation for converting // a JWT into a session state. -func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessionsapi.SessionState, error) { +func createSessionFromToken(ctx context.Context, token string, verify middlewareapi.VerifyFunc) (*sessionsapi.SessionState, error) { var claims struct { Subject string `json:"sub"` Email string `json:"email"` @@ -143,6 +143,16 @@ func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, i PreferredUsername string `json:"preferred_username"` } + verifiedToken, err := verify(ctx, token) + if err != nil { + return nil, err + } + + idToken, ok := verifiedToken.(*oidc.IDToken) + if !ok { + return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token) + } + if err := idToken.Claims(&claims); err != nil { return nil, fmt.Errorf("failed to parse bearer token claims: %v", err) } @@ -159,8 +169,8 @@ func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, i Email: claims.Email, User: claims.Subject, PreferredUsername: claims.PreferredUsername, - AccessToken: rawIDToken, - IDToken: rawIDToken, + AccessToken: token, + IDToken: token, RefreshToken: "", ExpiresOn: &idToken.Expiry, } diff --git a/pkg/middleware/jwt_session_test.go b/pkg/middleware/jwt_session_test.go index b9503731..794c8488 100644 --- a/pkg/middleware/jwt_session_test.go +++ b/pkg/middleware/jwt_session_test.go @@ -73,13 +73,20 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` const validToken = "eyJfoobar.eyJfoobar.12345asdf" Context("JwtSessionLoader", func() { - var verifier *oidc.IDTokenVerifier + var verifier middlewareapi.VerifyFunc const nonVerifiedToken = validToken BeforeEach(func() { - keyset := noOpKeySet{} - verifier = oidc.NewVerifier("https://issuer.example.com", keyset, - &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) + verifier = func(ctx context.Context, token string) (interface{}, error) { + return oidc.NewVerifier( + "https://issuer.example.com", + noOpKeySet{}, + &oidc.Config{ + ClientID: "https://test.myapp.com", + SkipExpiryCheck: true, + }, + ).Verify(ctx, token) + } }) type jwtSessionLoaderTableInput struct { @@ -167,16 +174,23 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` const nonVerifiedToken = validToken BeforeEach(func() { - keyset := noOpKeySet{} - verifier := oidc.NewVerifier("https://issuer.example.com", keyset, - &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) + verifier := func(ctx context.Context, token string) (interface{}, error) { + return oidc.NewVerifier( + "https://issuer.example.com", + noOpKeySet{}, + &oidc.Config{ + ClientID: "https://test.myapp.com", + SkipExpiryCheck: true, + }, + ).Verify(ctx, token) + } j = &jwtSessionLoader{ jwtRegex: regexp.MustCompile(jwtRegexFormat), sessionLoaders: []middlewareapi.TokenToSessionLoader{ { Verifier: verifier, - TokenToSession: createSessionStateFromBearerToken, + TokenToSession: createSessionFromToken, }, }, } @@ -239,7 +253,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` ) }) - Context("findBearerTokenFromHeader", func() { + Context("findTokenFromHeader", func() { var j *jwtSessionLoader BeforeEach(func() { @@ -256,7 +270,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` DescribeTable("with a header", func(in findBearerTokenFromHeaderTableInput) { - token, err := j.findBearerTokenFromHeader(in.header) + token, err := j.findTokenFromHeader(in.header) if in.expectedErr != nil { Expect(err).To(MatchError(in.expectedErr)) } else { @@ -381,7 +395,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` ) }) - Context("createSessionStateFromBearerToken", func() { + Context("createSessionFromToken", func() { ctx := context.Background() expiresFuture := time.Now().Add(time.Duration(5) * time.Minute) verified := true @@ -403,11 +417,18 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` DescribeTable("when creating a session from an IDToken", func(in createSessionStateTableInput) { - verifier := oidc.NewVerifier( - "https://issuer.example.com", - noOpKeySet{}, - &oidc.Config{ClientID: "asdf1234"}, - ) + verifier := func(ctx context.Context, token string) (interface{}, error) { + oidcVerifier := oidc.NewVerifier( + "https://issuer.example.com", + noOpKeySet{}, + &oidc.Config{ClientID: "asdf1234"}, + ) + + idToken, err := oidcVerifier.Verify(ctx, token) + Expect(err).ToNot(HaveOccurred()) + + return idToken, nil + } key, err := rsa.GenerateKey(rand.Reader, 2048) Expect(err).ToNot(HaveOccurred()) @@ -415,11 +436,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` rawIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS256, in.idToken).SignedString(key) Expect(err).ToNot(HaveOccurred()) - // Pass to a dummy Verifier to get an oidc.IDToken from the rawIDToken for our actual test below - idToken, err := verifier.Verify(context.Background(), rawIDToken) - Expect(err).ToNot(HaveOccurred()) - - session, err := createSessionStateFromBearerToken(ctx, rawIDToken, idToken) + session, err := createSessionFromToken(ctx, rawIDToken, verifier) if in.expectedErr != nil { Expect(err).To(MatchError(in.expectedErr)) Expect(session).To(BeNil()) diff --git a/providers/oidc.go b/providers/oidc.go index 7c48c42a..d94f27ce 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -11,6 +11,7 @@ import ( oidc "github.com/coreos/go-oidc" "golang.org/x/oauth2" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" @@ -175,14 +176,24 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok return newSession, nil } -func (p *OIDCProvider) CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) { +func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) { + verifiedToken, err := verify(ctx, token) + if err != nil { + return nil, err + } + + idToken, ok := verifiedToken.(*oidc.IDToken) + if !ok { + return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token) + } + newSession, err := p.createSessionStateInternal(ctx, idToken, nil) if err != nil { return nil, err } - newSession.AccessToken = rawIDToken - newSession.IDToken = rawIDToken + newSession.AccessToken = token + newSession.IDToken = token newSession.RefreshToken = "" newSession.ExpiresOn = &idToken.Expiry diff --git a/providers/oidc_test.go b/providers/oidc_test.go index cc4cdc8a..429a7fca 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -347,14 +347,18 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) { rawIDToken, err := newSignedTestIDToken(tc.IDToken) assert.NoError(t, err) - keyset := fakeKeySetStub{} - verifier := oidc.NewVerifier("https://issuer.example.com", keyset, - &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) + verifyFunc := func(ctx context.Context, token string) (interface{}, error) { + keyset := fakeKeySetStub{} + verifier := oidc.NewVerifier("https://issuer.example.com", keyset, + &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) - idToken, err := verifier.Verify(context.Background(), rawIDToken) - assert.NoError(t, err) + idToken, err := verifier.Verify(ctx, token) + assert.NoError(t, err) - ss, err := provider.CreateSessionFromBearer(context.Background(), rawIDToken, idToken) + return idToken, nil + } + + ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken, verifyFunc) assert.NoError(t, err) assert.Equal(t, tc.ExpectedUser, ss.User) diff --git a/providers/provider_default.go b/providers/provider_default.go index 7a8c4e40..ee2e2824 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -8,8 +8,7 @@ import ( "net/url" "time" - "github.com/coreos/go-oidc" - + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" ) @@ -127,6 +126,6 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses // CreateSessionStateFromBearerToken should be implemented to allow providers // to convert ID tokens into sessions -func (p *ProviderData) CreateSessionFromBearer(_ context.Context, _ string, _ *oidc.IDToken) (*sessions.SessionState, error) { +func (p *ProviderData) CreateSessionFromToken(_ context.Context, _ string, _ middleware.VerifyFunc) (*sessions.SessionState, error) { return nil, ErrNotImplemented } diff --git a/providers/providers.go b/providers/providers.go index 09abf725..8890a5a7 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -3,7 +3,7 @@ package providers import ( "context" - "github.com/coreos/go-oidc" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" ) @@ -18,7 +18,7 @@ type Provider interface { ValidateSession(ctx context.Context, s *sessions.SessionState) bool GetLoginURL(redirectURI, finalRedirect string) string RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) - CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) + CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) } // New provides a new Provider based on the configured provider string From 44fa8316a170af2f2752df6bab76eea54348a314 Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Fri, 23 Oct 2020 23:43:27 -0700 Subject: [PATCH 3/6] Aggregate error logging on JWT chain failures --- CHANGELOG.md | 5 ++- go.mod | 7 +-- go.sum | 69 ++++++++++++++++++++++++++++++ pkg/middleware/jwt_session.go | 7 ++- pkg/middleware/jwt_session_test.go | 15 +++++-- 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5047a8c0..284978c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,11 +43,12 @@ ## Changes since v6.1.1 -- [#925](https://github.com/oauth2-proxy/oauth2-rpoxy/pull/925) Fix basic auth legacy header conversion (@JoelSpeed) -- [#916](https://github.com/oauth2-proxy/oauth2-rpoxy/pull/916) Add AlphaOptions struct to prepare for alpha config loading (@JoelSpeed) +- [#925](https://github.com/oauth2-proxy/oauth2-proxy/pull/925) Fix basic auth legacy header conversion (@JoelSpeed) +- [#916](https://github.com/oauth2-proxy/oauth2-proxy/pull/916) Add AlphaOptions struct to prepare for alpha config loading (@JoelSpeed) - [#923](https://github.com/oauth2-proxy/oauth2-proxy/pull/923) Support TLS 1.3 (@aajisaka) - [#918](https://github.com/oauth2-proxy/oauth2-proxy/pull/918) Fix log header output (@JoelSpeed) - [#911](https://github.com/oauth2-proxy/oauth2-proxy/pull/911) Validate provider type on startup. +- [#869](https://github.com/oauth2-proxy/oauth2-proxy/pull/869) Streamline provider interface method names and signatures (@NickMeves) - [#906](https://github.com/oauth2-proxy/oauth2-proxy/pull/906) Set up v6.1.x versioned documentation as default documentation (@JoelSpeed) - [#905](https://github.com/oauth2-proxy/oauth2-proxy/pull/905) Remove v5 legacy sessions support (@NickMeves) - [#904](https://github.com/oauth2-proxy/oauth2-proxy/pull/904) Set `skip-auth-strip-headers` to `true` by default (@NickMeves) diff --git a/go.mod b/go.mod index fcacdcc1..ae5dadd5 100644 --- a/go.mod +++ b/go.mod @@ -19,15 +19,16 @@ require ( github.com/onsi/gomega v1.10.2 github.com/pierrec/lz4 v2.5.2+incompatible github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect - github.com/spf13/pflag v1.0.3 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.6.3 github.com/stretchr/testify v1.6.1 github.com/vmihailenco/msgpack/v4 v4.3.11 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d google.golang.org/api v0.20.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/square/go-jose.v2 v2.4.1 + k8s.io/apimachinery v0.19.3 ) diff --git a/go.sum b/go.sum index 08c16307..ed64398e 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,10 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc= github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= @@ -48,36 +51,52 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-redis/redis/v8 v8.2.3 h1:eNesND+DWt/sjQOtPFxAbQkTIXaXX00qNLxjVWkZ70k= github.com/go-redis/redis/v8 v8.2.3/go.mod h1:ysgGY09J/QeDYbu3HikWEIPCwaeOkuNoTgKayTEaEOw= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= @@ -92,6 +111,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -99,6 +119,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -112,7 +133,9 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -120,6 +143,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -131,6 +155,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -139,16 +164,23 @@ github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg+ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= @@ -158,6 +190,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= @@ -186,14 +219,18 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -223,6 +260,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -237,12 +276,16 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -260,6 +303,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -267,14 +311,20 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -296,6 +346,8 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -306,13 +358,20 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= @@ -326,6 +385,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= @@ -333,3 +393,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc= +k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/pkg/middleware/jwt_session.go b/pkg/middleware/jwt_session.go index 5e99e0df..ef7d9bce 100644 --- a/pkg/middleware/jwt_session.go +++ b/pkg/middleware/jwt_session.go @@ -11,6 +11,7 @@ import ( middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" + "k8s.io/apimachinery/pkg/util/errors" ) const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` @@ -80,15 +81,17 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio return nil, err } + errs := []error{fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))} for _, loader := range j.sessionLoaders { session, err := loader.TokenToSession(req.Context(), token, loader.Verifier) if err == nil { return session, nil + } else { + errs = append(errs, err) } } - // TODO (@NickMeves) Aggregate error logs in the chain - return nil, fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization")) + return nil, errors.NewAggregate(errs) } // findTokenFromHeader finds a valid JWT token from the Authorization header of a given request. diff --git a/pkg/middleware/jwt_session_test.go b/pkg/middleware/jwt_session_test.go index 794c8488..213f0720 100644 --- a/pkg/middleware/jwt_session_test.go +++ b/pkg/middleware/jwt_session_test.go @@ -20,6 +20,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + k8serrors "k8s.io/apimachinery/pkg/util/errors" ) type noOpKeySet struct { @@ -232,8 +233,11 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` }), Entry("Bearer ", getJWTSessionTableInput{ authorizationHeader: fmt.Sprintf("Bearer %s", nonVerifiedToken), - expectedErr: errors.New("unable to verify jwt token: \"Bearer eyJfoobar.eyJfoobar.12345asdf\""), - expectedSession: nil, + expectedErr: k8serrors.NewAggregate([]error{ + errors.New("unable to verify jwt token: \"Bearer eyJfoobar.eyJfoobar.12345asdf\""), + errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"), + }), + expectedSession: nil, }), Entry("Bearer ", getJWTSessionTableInput{ authorizationHeader: fmt.Sprintf("Bearer %s", verifiedToken), @@ -242,8 +246,11 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` }), Entry("Basic Base64(:) (No password)", getJWTSessionTableInput{ authorizationHeader: "Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6", - expectedErr: errors.New("unable to verify jwt token: \"Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6\""), - expectedSession: nil, + expectedErr: k8serrors.NewAggregate([]error{ + errors.New("unable to verify jwt token: \"Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6\""), + errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"), + }), + expectedSession: nil, }), Entry("Basic Base64(:x-oauth-basic) (Sentinel password)", getJWTSessionTableInput{ authorizationHeader: fmt.Sprintf("Basic %s", verifiedTokenXOAuthBasicBase64), From 22f60e9b63e54e8a0fc6b625280f05159b5560a5 Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Sun, 15 Nov 2020 18:57:48 -0800 Subject: [PATCH 4/6] Generalize and extend default CreateSessionFromToken --- oauthproxy.go | 112 +++++++++++++---------------- pkg/apis/middleware/session.go | 60 ++++++++++++---- pkg/middleware/jwt_session.go | 62 +--------------- pkg/middleware/jwt_session_test.go | 67 ++++++++--------- pkg/validation/options.go | 11 ++- providers/oidc.go | 11 +-- providers/oidc_test.go | 24 ++----- providers/provider_data.go | 2 + providers/provider_default.go | 5 +- providers/providers.go | 3 +- 10 files changed, 148 insertions(+), 209 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index d546f005..693d5a7a 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -13,7 +13,6 @@ import ( "strings" "time" - "github.com/coreos/go-oidc" "github.com/justinas/alice" ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" @@ -78,36 +77,34 @@ type OAuthProxy struct { AuthOnlyPath string UserInfoPath string - allowedRoutes []allowedRoute - redirectURL *url.URL // the url to receive requests at - whitelistDomains []string - provider providers.Provider - providerNameOverride string - sessionStore sessionsapi.SessionStore - ProxyPrefix string - SignInMessage string - basicAuthValidator basic.Validator - displayHtpasswdForm bool - serveMux http.Handler - SetXAuthRequest bool - PassBasicAuth bool - SetBasicAuth bool - SkipProviderButton bool - PassUserHeaders bool - BasicAuthPassword string - PassAccessToken bool - SetAuthorization bool - PassAuthorization bool - PreferEmailToUser bool - skipAuthPreflight bool - skipJwtBearerTokens bool - mainJwtBearerVerifier *oidc.IDTokenVerifier - extraJwtBearerVerifiers []*oidc.IDTokenVerifier - templates *template.Template - realClientIPParser ipapi.RealClientIPParser - trustedIPs *ip.NetSet - Banner string - Footer string + allowedRoutes []allowedRoute + redirectURL *url.URL // the url to receive requests at + whitelistDomains []string + provider providers.Provider + providerNameOverride string + sessionStore sessionsapi.SessionStore + ProxyPrefix string + SignInMessage string + basicAuthValidator basic.Validator + displayHtpasswdForm bool + serveMux http.Handler + SetXAuthRequest bool + PassBasicAuth bool + SetBasicAuth bool + SkipProviderButton bool + PassUserHeaders bool + BasicAuthPassword string + PassAccessToken bool + SetAuthorization bool + PassAuthorization bool + PreferEmailToUser bool + skipAuthPreflight bool + skipJwtBearerTokens bool + templates *template.Template + realClientIPParser ipapi.RealClientIPParser + trustedIPs *ip.NetSet + Banner string + Footer string sessionChain alice.Chain headersChain alice.Chain @@ -202,25 +199,23 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), - ProxyPrefix: opts.ProxyPrefix, - provider: opts.GetProvider(), - providerNameOverride: opts.ProviderName, - sessionStore: sessionStore, - serveMux: upstreamProxy, - redirectURL: redirectURL, - allowedRoutes: allowedRoutes, - whitelistDomains: opts.WhitelistDomains, - skipAuthPreflight: opts.SkipAuthPreflight, - skipJwtBearerTokens: opts.SkipJwtBearerTokens, - mainJwtBearerVerifier: opts.GetOIDCVerifier(), - extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(), - realClientIPParser: opts.GetRealClientIPParser(), - SkipProviderButton: opts.SkipProviderButton, - templates: templates, - trustedIPs: trustedIPs, - Banner: opts.Banner, - Footer: opts.Footer, - SignInMessage: buildSignInMessage(opts), + ProxyPrefix: opts.ProxyPrefix, + provider: opts.GetProvider(), + providerNameOverride: opts.ProviderName, + sessionStore: sessionStore, + serveMux: upstreamProxy, + redirectURL: redirectURL, + allowedRoutes: allowedRoutes, + whitelistDomains: opts.WhitelistDomains, + skipAuthPreflight: opts.SkipAuthPreflight, + skipJwtBearerTokens: opts.SkipJwtBearerTokens, + realClientIPParser: opts.GetRealClientIPParser(), + SkipProviderButton: opts.SkipProviderButton, + templates: templates, + trustedIPs: trustedIPs, + Banner: opts.Banner, + Footer: opts.Footer, + SignInMessage: buildSignInMessage(opts), basicAuthValidator: basicAuthValidator, displayHtpasswdForm: basicAuthValidator != nil && opts.DisplayHtpasswdForm, @@ -266,22 +261,13 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt chain := alice.New() if opts.SkipJwtBearerTokens { - sessionLoaders := []middlewareapi.TokenToSessionLoader{} - if opts.GetOIDCVerifier() != nil { - sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{ - Verifier: func(ctx context.Context, token string) (interface{}, error) { - return opts.GetOIDCVerifier().Verify(ctx, token) - }, - TokenToSession: opts.GetProvider().CreateSessionFromToken, - }) + sessionLoaders := []middlewareapi.TokenToSessionFunc{ + opts.GetProvider().CreateSessionFromToken, } for _, verifier := range opts.GetJWTBearerVerifiers() { - sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{ - Verifier: func(ctx context.Context, token string) (interface{}, error) { - return verifier.Verify(ctx, token) - }, - }) + sessionLoaders = append(sessionLoaders, + middlewareapi.CreateTokenToSessionFunc(verifier.Verify)) } chain = chain.Append(middleware.NewJwtSessionLoader(sessionLoaders)) diff --git a/pkg/apis/middleware/session.go b/pkg/apis/middleware/session.go index a8a3bbea..8650d5c2 100644 --- a/pkg/apis/middleware/session.go +++ b/pkg/apis/middleware/session.go @@ -2,25 +2,57 @@ package middleware import ( "context" + "fmt" + "github.com/coreos/go-oidc" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" ) -// TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a -// SessionState. -type TokenToSessionFunc func(ctx context.Context, token string, verify VerifyFunc) (*sessionsapi.SessionState, error) +// TokenToSessionFunc takes a raw ID Token and converts it into a SessionState. +type TokenToSessionFunc func(ctx context.Context, token string) (*sessionsapi.SessionState, error) -// VerifyFunc takes a raw bearer token and verifies it -type VerifyFunc func(ctx context.Context, token string) (interface{}, error) +// VerifyFunc takes a raw bearer token and verifies it returning the converted +// oidc.IDToken representation of the token. +type VerifyFunc func(ctx context.Context, token string) (*oidc.IDToken, error) -// TokenToSessionLoader pairs a token verifier with the correct converter function -// to convert the ID Token to a SessionState. -type TokenToSessionLoader struct { - // Verifier is used to verify that the ID Token was signed by the claimed issuer - // and that the token has not been tampered with. - Verifier VerifyFunc +// CreateTokenToSessionFunc provides a handler that is a default implementation +// for converting a JWT into a session. +func CreateTokenToSessionFunc(verify VerifyFunc) TokenToSessionFunc { + return func(ctx context.Context, token string) (*sessionsapi.SessionState, error) { + var claims struct { + Subject string `json:"sub"` + Email string `json:"email"` + Verified *bool `json:"email_verified"` + PreferredUsername string `json:"preferred_username"` + } - // TokenToSession converts a raw bearer token to a SessionState. - // (Optional) If not set a default basic implementation is used. - TokenToSession TokenToSessionFunc + idToken, err := verify(ctx, token) + if err != nil { + return nil, err + } + + if err := idToken.Claims(&claims); err != nil { + return nil, fmt.Errorf("failed to parse bearer token claims: %v", err) + } + + if claims.Email == "" { + claims.Email = claims.Subject + } + + if claims.Verified != nil && !*claims.Verified { + return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) + } + + newSession := &sessionsapi.SessionState{ + Email: claims.Email, + User: claims.Subject, + PreferredUsername: claims.PreferredUsername, + AccessToken: token, + IDToken: token, + RefreshToken: "", + ExpiresOn: &idToken.Expiry, + } + + return newSession, nil + } } diff --git a/pkg/middleware/jwt_session.go b/pkg/middleware/jwt_session.go index ef7d9bce..f9e137e8 100644 --- a/pkg/middleware/jwt_session.go +++ b/pkg/middleware/jwt_session.go @@ -1,12 +1,10 @@ package middleware import ( - "context" "fmt" "net/http" "regexp" - "github.com/coreos/go-oidc" "github.com/justinas/alice" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" @@ -16,16 +14,7 @@ import ( const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` -func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) alice.Constructor { - for i, loader := range sessionLoaders { - if loader.TokenToSession == nil { - sessionLoaders[i] = middlewareapi.TokenToSessionLoader{ - Verifier: loader.Verifier, - TokenToSession: createSessionFromToken, - } - } - } - +func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionFunc) alice.Constructor { js := &jwtSessionLoader{ jwtRegex: regexp.MustCompile(jwtRegexFormat), sessionLoaders: sessionLoaders, @@ -37,7 +26,7 @@ func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) al // Authorization headers. type jwtSessionLoader struct { jwtRegex *regexp.Regexp - sessionLoaders []middlewareapi.TokenToSessionLoader + sessionLoaders []middlewareapi.TokenToSessionFunc } // loadSession attempts to load a session from a JWT stored in an Authorization @@ -83,7 +72,7 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio errs := []error{fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))} for _, loader := range j.sessionLoaders { - session, err := loader.TokenToSession(req.Context(), token, loader.Verifier) + session, err := loader(req.Context(), token) if err == nil { return session, nil } else { @@ -135,48 +124,3 @@ func (j *jwtSessionLoader) getBasicToken(token string) (string, error) { return "", fmt.Errorf("invalid basic auth token found in authorization header") } - -// createSessionFromToken is a default implementation for converting -// a JWT into a session state. -func createSessionFromToken(ctx context.Context, token string, verify middlewareapi.VerifyFunc) (*sessionsapi.SessionState, error) { - var claims struct { - Subject string `json:"sub"` - Email string `json:"email"` - Verified *bool `json:"email_verified"` - PreferredUsername string `json:"preferred_username"` - } - - verifiedToken, err := verify(ctx, token) - if err != nil { - return nil, err - } - - idToken, ok := verifiedToken.(*oidc.IDToken) - if !ok { - return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token) - } - - if err := idToken.Claims(&claims); err != nil { - return nil, fmt.Errorf("failed to parse bearer token claims: %v", err) - } - - if claims.Email == "" { - claims.Email = claims.Subject - } - - if claims.Verified != nil && !*claims.Verified { - return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) - } - - newSession := &sessionsapi.SessionState{ - Email: claims.Email, - User: claims.Subject, - PreferredUsername: claims.PreferredUsername, - AccessToken: token, - IDToken: token, - RefreshToken: "", - ExpiresOn: &idToken.Expiry, - } - - return newSession, nil -} diff --git a/pkg/middleware/jwt_session_test.go b/pkg/middleware/jwt_session_test.go index 213f0720..aac5d5af 100644 --- a/pkg/middleware/jwt_session_test.go +++ b/pkg/middleware/jwt_session_test.go @@ -26,7 +26,7 @@ import ( type noOpKeySet struct { } -func (noOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) { +func (noOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) { splitStrings := strings.Split(jwt, ".") payloadString := splitStrings[1] return base64.RawURLEncoding.DecodeString(payloadString) @@ -78,16 +78,14 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` const nonVerifiedToken = validToken BeforeEach(func() { - verifier = func(ctx context.Context, token string) (interface{}, error) { - return oidc.NewVerifier( - "https://issuer.example.com", - noOpKeySet{}, - &oidc.Config{ - ClientID: "https://test.myapp.com", - SkipExpiryCheck: true, - }, - ).Verify(ctx, token) - } + verifier = oidc.NewVerifier( + "https://issuer.example.com", + noOpKeySet{}, + &oidc.Config{ + ClientID: "https://test.myapp.com", + SkipExpiryCheck: true, + }, + ).Verify }) type jwtSessionLoaderTableInput struct { @@ -110,10 +108,8 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` rw := httptest.NewRecorder() - sessionLoaders := []middlewareapi.TokenToSessionLoader{ - { - Verifier: verifier, - }, + sessionLoaders := []middlewareapi.TokenToSessionFunc{ + middlewareapi.CreateTokenToSessionFunc(verifier), } // Create the handler with a next handler that will capture the session @@ -175,24 +171,19 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` const nonVerifiedToken = validToken BeforeEach(func() { - verifier := func(ctx context.Context, token string) (interface{}, error) { - return oidc.NewVerifier( - "https://issuer.example.com", - noOpKeySet{}, - &oidc.Config{ - ClientID: "https://test.myapp.com", - SkipExpiryCheck: true, - }, - ).Verify(ctx, token) - } + verifier := oidc.NewVerifier( + "https://issuer.example.com", + noOpKeySet{}, + &oidc.Config{ + ClientID: "https://test.myapp.com", + SkipExpiryCheck: true, + }, + ).Verify j = &jwtSessionLoader{ jwtRegex: regexp.MustCompile(jwtRegexFormat), - sessionLoaders: []middlewareapi.TokenToSessionLoader{ - { - Verifier: verifier, - TokenToSession: createSessionFromToken, - }, + sessionLoaders: []middlewareapi.TokenToSessionFunc{ + middlewareapi.CreateTokenToSessionFunc(verifier), }, } }) @@ -402,7 +393,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` ) }) - Context("createSessionFromToken", func() { + Context("CreateTokenToSessionFunc", func() { ctx := context.Background() expiresFuture := time.Now().Add(time.Duration(5) * time.Minute) verified := true @@ -414,7 +405,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` jwt.StandardClaims } - type createSessionStateTableInput struct { + type tokenToSessionTableInput struct { idToken idTokenClaims expectedErr error expectedUser string @@ -423,8 +414,8 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` } DescribeTable("when creating a session from an IDToken", - func(in createSessionStateTableInput) { - verifier := func(ctx context.Context, token string) (interface{}, error) { + func(in tokenToSessionTableInput) { + verifier := func(ctx context.Context, token string) (*oidc.IDToken, error) { oidcVerifier := oidc.NewVerifier( "https://issuer.example.com", noOpKeySet{}, @@ -443,7 +434,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` rawIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS256, in.idToken).SignedString(key) Expect(err).ToNot(HaveOccurred()) - session, err := createSessionFromToken(ctx, rawIDToken, verifier) + session, err := middlewareapi.CreateTokenToSessionFunc(verifier)(ctx, rawIDToken) if in.expectedErr != nil { Expect(err).To(MatchError(in.expectedErr)) Expect(session).To(BeNil()) @@ -459,7 +450,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` Expect(session.RefreshToken).To(BeEmpty()) Expect(session.PreferredUsername).To(BeEmpty()) }, - Entry("with no email", createSessionStateTableInput{ + Entry("with no email", tokenToSessionTableInput{ idToken: idTokenClaims{ StandardClaims: jwt.StandardClaims{ Audience: "asdf1234", @@ -476,7 +467,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` expectedEmail: "123456789", expectedExpires: &expiresFuture, }), - Entry("with a verified email", createSessionStateTableInput{ + Entry("with a verified email", tokenToSessionTableInput{ idToken: idTokenClaims{ StandardClaims: jwt.StandardClaims{ Audience: "asdf1234", @@ -495,7 +486,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` expectedEmail: "foo@example.com", expectedExpires: &expiresFuture, }), - Entry("with a non-verified email", createSessionStateTableInput{ + Entry("with a non-verified email", tokenToSessionTableInput{ idToken: idTokenClaims{ StandardClaims: jwt.StandardClaims{ Audience: "asdf1234", diff --git a/pkg/validation/options.go b/pkg/validation/options.go index 839c2035..15cd374e 100644 --- a/pkg/validation/options.go +++ b/pkg/validation/options.go @@ -233,6 +233,9 @@ func parseProviderInfo(o *options.Options, msgs []string) []string { p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs) p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs) + // Make the OIDC Verifier accessible to all providers that can support it + p.Verifier = o.GetOIDCVerifier() + p.SetAllowedGroups(o.AllowedGroups) provider := providers.New(o.ProviderType, p) @@ -273,18 +276,14 @@ func parseProviderInfo(o *options.Options, msgs []string) []string { p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail p.UserIDClaim = o.UserIDClaim p.GroupsClaim = o.OIDCGroupsClaim - if o.GetOIDCVerifier() == nil { + if p.Verifier == nil { msgs = append(msgs, "oidc provider requires an oidc issuer URL") - } else { - p.Verifier = o.GetOIDCVerifier() } case *providers.GitLabProvider: p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail p.Groups = o.GitLabGroup - if o.GetOIDCVerifier() != nil { - p.Verifier = o.GetOIDCVerifier() - } else { + if p.Verifier == nil { // Initialize with default verifier for gitlab.com ctx := context.Background() diff --git a/providers/oidc.go b/providers/oidc.go index d94f27ce..704e3341 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -11,7 +11,6 @@ import ( oidc "github.com/coreos/go-oidc" "golang.org/x/oauth2" - "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" @@ -23,7 +22,6 @@ const emailClaim = "email" type OIDCProvider struct { *ProviderData - Verifier *oidc.IDTokenVerifier AllowUnverifiedEmail bool UserIDClaim string GroupsClaim string @@ -176,17 +174,12 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok return newSession, nil } -func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) { - verifiedToken, err := verify(ctx, token) +func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) { + idToken, err := p.Verifier.Verify(ctx, token) if err != nil { return nil, err } - idToken, ok := verifiedToken.(*oidc.IDToken) - if !ok { - return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token) - } - newSession, err := p.createSessionStateInternal(ctx, idToken, nil) if err != nil { return nil, err diff --git a/providers/oidc_test.go b/providers/oidc_test.go index 429a7fca..a4048d89 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -144,16 +144,17 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider { Scheme: serverURL.Scheme, Host: serverURL.Host, Path: "/api"}, - Scope: "openid profile offline_access"} - - p := &OIDCProvider{ - ProviderData: providerData, + Scope: "openid profile offline_access", Verifier: oidc.NewVerifier( "https://issuer.example.com", fakeKeySetStub{}, &oidc.Config{ClientID: clientID}, ), - UserIDClaim: "email", + } + + p := &OIDCProvider{ + ProviderData: providerData, + UserIDClaim: "email", } return p @@ -347,18 +348,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) { rawIDToken, err := newSignedTestIDToken(tc.IDToken) assert.NoError(t, err) - verifyFunc := func(ctx context.Context, token string) (interface{}, error) { - keyset := fakeKeySetStub{} - verifier := oidc.NewVerifier("https://issuer.example.com", keyset, - &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) - - idToken, err := verifier.Verify(ctx, token) - assert.NoError(t, err) - - return idToken, nil - } - - ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken, verifyFunc) + ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken) assert.NoError(t, err) assert.Equal(t, tc.ExpectedUser, ss.User) diff --git a/providers/provider_data.go b/providers/provider_data.go index 0881a1c6..330df7ca 100644 --- a/providers/provider_data.go +++ b/providers/provider_data.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/url" + "github.com/coreos/go-oidc" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" ) @@ -26,6 +27,7 @@ type ProviderData struct { ClientSecretFile string Scope string Prompt string + Verifier *oidc.IDTokenVerifier // Universal Group authorization data structure // any provider can set to consume diff --git a/providers/provider_default.go b/providers/provider_default.go index ee2e2824..415cdd73 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -126,6 +126,9 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses // CreateSessionStateFromBearerToken should be implemented to allow providers // to convert ID tokens into sessions -func (p *ProviderData) CreateSessionFromToken(_ context.Context, _ string, _ middleware.VerifyFunc) (*sessions.SessionState, error) { +func (p *ProviderData) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) { + if p.Verifier != nil { + return middleware.CreateTokenToSessionFunc(p.Verifier.Verify)(ctx, token) + } return nil, ErrNotImplemented } diff --git a/providers/providers.go b/providers/providers.go index 8890a5a7..0087e4cc 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -3,7 +3,6 @@ package providers import ( "context" - "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" ) @@ -18,7 +17,7 @@ type Provider interface { ValidateSession(ctx context.Context, s *sessions.SessionState) bool GetLoginURL(redirectURI, finalRedirect string) string RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) - CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) + CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) } // New provides a new Provider based on the configured provider string From 5f8f8562602969701b604980289a87977e0e4c5f Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Thu, 26 Nov 2020 11:47:44 -0800 Subject: [PATCH 5/6] Remove failed bearer tokens from logs --- pkg/middleware/jwt_session.go | 14 ++++++++------ pkg/middleware/jwt_session_test.go | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/middleware/jwt_session.go b/pkg/middleware/jwt_session.go index f9e137e8..0510c72a 100644 --- a/pkg/middleware/jwt_session.go +++ b/pkg/middleware/jwt_session.go @@ -1,6 +1,7 @@ package middleware import ( + "errors" "fmt" "net/http" "regexp" @@ -9,7 +10,7 @@ import ( middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" - "k8s.io/apimachinery/pkg/util/errors" + k8serrors "k8s.io/apimachinery/pkg/util/errors" ) const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` @@ -70,17 +71,18 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio return nil, err } - errs := []error{fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))} + // This leading error message only occurs if all session loaders fail + errs := []error{errors.New("unable to verify bearer token")} for _, loader := range j.sessionLoaders { session, err := loader(req.Context(), token) - if err == nil { - return session, nil - } else { + if err != nil { errs = append(errs, err) + continue } + return session, nil } - return nil, errors.NewAggregate(errs) + return nil, k8serrors.NewAggregate(errs) } // findTokenFromHeader finds a valid JWT token from the Authorization header of a given request. diff --git a/pkg/middleware/jwt_session_test.go b/pkg/middleware/jwt_session_test.go index aac5d5af..cd34c5ad 100644 --- a/pkg/middleware/jwt_session_test.go +++ b/pkg/middleware/jwt_session_test.go @@ -225,7 +225,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` Entry("Bearer ", getJWTSessionTableInput{ authorizationHeader: fmt.Sprintf("Bearer %s", nonVerifiedToken), expectedErr: k8serrors.NewAggregate([]error{ - errors.New("unable to verify jwt token: \"Bearer eyJfoobar.eyJfoobar.12345asdf\""), + errors.New("unable to verify bearer token"), errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"), }), expectedSession: nil, @@ -238,7 +238,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=` Entry("Basic Base64(:) (No password)", getJWTSessionTableInput{ authorizationHeader: "Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6", expectedErr: k8serrors.NewAggregate([]error{ - errors.New("unable to verify jwt token: \"Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6\""), + errors.New("unable to verify bearer token"), errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"), }), expectedSession: nil, From 57a8ef06b48f1e92c32c4a7ffba13761bfd49809 Mon Sep 17 00:00:00 2001 From: Nick Meves Date: Thu, 26 Nov 2020 11:53:41 -0800 Subject: [PATCH 6/6] Fix method renaming in comments and tests --- providers/oidc_test.go | 2 +- providers/provider_default.go | 3 +-- providers/provider_default_test.go | 7 +++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/providers/oidc_test.go b/providers/oidc_test.go index a4048d89..54df1b31 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -299,7 +299,7 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) { assert.Equal(t, refreshToken, existingSession.RefreshToken) } -func TestCreateSessionStateFromBearerToken(t *testing.T) { +func TestOIDCProviderCreateSessionFromToken(t *testing.T) { const profileURLEmail = "janed@me.com" testCases := map[string]struct { diff --git a/providers/provider_default.go b/providers/provider_default.go index 415cdd73..e7f00917 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -124,8 +124,7 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses return false, nil } -// CreateSessionStateFromBearerToken should be implemented to allow providers -// to convert ID tokens into sessions +// CreateSessionFromToken converts Bearer IDTokens into sessions func (p *ProviderData) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) { if p.Verifier != nil { return middleware.CreateTokenToSessionFunc(p.Verifier.Verify)(ctx, token) diff --git a/providers/provider_default_test.go b/providers/provider_default_test.go index 5f02ecbb..df1525cf 100644 --- a/providers/provider_default_test.go +++ b/providers/provider_default_test.go @@ -49,10 +49,13 @@ func TestAcrValuesConfigured(t *testing.T) { assert.Contains(t, result, "acr_values=testValue") } -func TestEnrichSessionState(t *testing.T) { +func TestProviderDataEnrichSession(t *testing.T) { + g := NewWithT(t) p := &ProviderData{} s := &sessions.SessionState{} - assert.NoError(t, p.EnrichSession(context.Background(), s)) + + err := p.EnrichSession(context.Background(), s) + g.Expect(err).ToNot(HaveOccurred()) } func TestProviderDataAuthorize(t *testing.T) {