git.iko.soy/typeable/oauth2-proxy
on master help sign in
Decouple TokenToSession from OIDC & add a generic VerifyFunc
commit3e9717d489b1ce527ac48379584594e58ca9dce6 authorNick Meves <nick.meves@greenhouse.io> dateFri 23 Oct 2020, 23:34 (6 years ago) parente9f7879
8 files changed+102−55
oauthproxy.go+7−3 pkg/apis/middleware/session.go+7−5 pkg/middleware/jwt_session.go+22−12 pkg/middleware/jwt_session_test.go+38−21 providers/oidc.go+14−3 providers/oidc_test.go+10−6 providers/provider_default.go+2−3 providers/providers.go+2−2
oauthproxy.go+7−3
@@ -269,14 +269,18 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt
269269 sessionLoaders := []middlewareapi.TokenToSessionLoader{}
270270 if opts.GetOIDCVerifier() != nil {
271271 sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
272− Verifier: opts.GetOIDCVerifier(),
273− TokenToSession: opts.GetProvider().CreateSessionFromBearer,
272+ Verifier: func(ctx context.Context, token string) (interface{}, error) {
273+ return opts.GetOIDCVerifier().Verify(ctx, token)
274+ },
275+ TokenToSession: opts.GetProvider().CreateSessionFromToken,
274276 })
275277 }
276278
277279 for _, verifier := range opts.GetJWTBearerVerifiers() {
278280 sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
279− Verifier: verifier,
281+ Verifier: func(ctx context.Context, token string) (interface{}, error) {
282+ return verifier.Verify(ctx, token)
283+ },
280284 })
281285 }
282286
pkg/apis/middleware/session.go+7−5
@@ -3,22 +3,24 @@ package middleware
33import (
44 "context"
55
6− "github.com/coreos/go-oidc"
76 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
87)
98
109// TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a
1110// SessionState.
12−type TokenToSessionFunc func(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessionsapi.SessionState, error)
11+type TokenToSessionFunc func(ctx context.Context, token string, verify VerifyFunc) (*sessionsapi.SessionState, error)
12+
13+// VerifyFunc takes a raw bearer token and verifies it
14+type VerifyFunc func(ctx context.Context, token string) (interface{}, error)
1315
1416// TokenToSessionLoader pairs a token verifier with the correct converter function
1517// to convert the ID Token to a SessionState.
1618type TokenToSessionLoader struct {
17− // Verfier is used to verify that the ID Token was signed by the claimed issuer
19+ // Verifier is used to verify that the ID Token was signed by the claimed issuer
1820 // and that the token has not been tampered with.
19− Verifier *oidc.IDTokenVerifier
21+ Verifier VerifyFunc
2022
21− // TokenToSession converts a rawIDToken and an idToken to a SessionState.
23+ // TokenToSession converts a raw bearer token to a SessionState.
2224 // (Optional) If not set a default basic implementation is used.
2325 TokenToSession TokenToSessionFunc
2426}
pkg/middleware/jwt_session.go+22−12
@@ -13,14 +13,14 @@ import (
1313 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1414)
1515
16−const jwtRegexFormat = `^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`
16+const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`
1717
1818func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) alice.Constructor {
1919 for i, loader := range sessionLoaders {
2020 if loader.TokenToSession == nil {
2121 sessionLoaders[i] = middlewareapi.TokenToSessionLoader{
2222 Verifier: loader.Verifier,
23− TokenToSession: createSessionStateFromBearerToken,
23+ TokenToSession: createSessionFromToken,
2424 }
2525 }
2626 }
@@ -75,24 +75,24 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio
7575 return nil, nil
7676 }
7777
78− rawBearerToken, err := j.findBearerTokenFromHeader(auth)
78+ token, err := j.findTokenFromHeader(auth)
7979 if err != nil {
8080 return nil, err
8181 }
8282
8383 for _, loader := range j.sessionLoaders {
84− bearerToken, err := loader.Verifier.Verify(req.Context(), rawBearerToken)
84+ session, err := loader.TokenToSession(req.Context(), token, loader.Verifier)
8585 if err == nil {
86− // The token was verified, convert it to a session
87− return loader.TokenToSession(req.Context(), rawBearerToken, bearerToken)
86+ return session, nil
8887 }
8988 }
9089
90+ // TODO (@NickMeves) Aggregate error logs in the chain
9191 return nil, fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))
9292}
9393
94−// findBearerTokenFromHeader finds a valid JWT token from the Authorization header of a given request.
95−func (j *jwtSessionLoader) findBearerTokenFromHeader(header string) (string, error) {
94+// findTokenFromHeader finds a valid JWT token from the Authorization header of a given request.
95+func (j *jwtSessionLoader) findTokenFromHeader(header string) (string, error) {
9696 tokenType, token, err := splitAuthHeader(header)
9797 if err != nil {
9898 return "", err
@@ -133,9 +133,9 @@ func (j *jwtSessionLoader) getBasicToken(token string) (string, error) {
133133 return "", fmt.Errorf("invalid basic auth token found in authorization header")
134134}
135135
136−// createSessionStateFromBearerToken is a default implementation for converting
136+// createSessionFromToken is a default implementation for converting
137137// a JWT into a session state.
138−func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessionsapi.SessionState, error) {
138+func createSessionFromToken(ctx context.Context, token string, verify middlewareapi.VerifyFunc) (*sessionsapi.SessionState, error) {
139139 var claims struct {
140140 Subject string `json:"sub"`
141141 Email string `json:"email"`
@@ -143,6 +143,16 @@ func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, i
143143 PreferredUsername string `json:"preferred_username"`
144144 }
145145
146+ verifiedToken, err := verify(ctx, token)
147+ if err != nil {
148+ return nil, err
149+ }
150+
151+ idToken, ok := verifiedToken.(*oidc.IDToken)
152+ if !ok {
153+ return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token)
154+ }
155+
146156 if err := idToken.Claims(&claims); err != nil {
147157 return nil, fmt.Errorf("failed to parse bearer token claims: %v", err)
148158 }
@@ -159,8 +169,8 @@ func createSessionStateFromBearerToken(ctx context.Context, rawIDToken string, i
159169 Email: claims.Email,
160170 User: claims.Subject,
161171 PreferredUsername: claims.PreferredUsername,
162− AccessToken: rawIDToken,
163− IDToken: rawIDToken,
172+ AccessToken: token,
173+ IDToken: token,
164174 RefreshToken: "",
165175 ExpiresOn: &idToken.Expiry,
166176 }
pkg/middleware/jwt_session_test.go+38−21
@@ -73,13 +73,20 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
7373 const validToken = "eyJfoobar.eyJfoobar.12345asdf"
7474
7575 Context("JwtSessionLoader", func() {
76− var verifier *oidc.IDTokenVerifier
76+ var verifier middlewareapi.VerifyFunc
7777 const nonVerifiedToken = validToken
7878
7979 BeforeEach(func() {
80− keyset := noOpKeySet{}
81− verifier = oidc.NewVerifier("https://issuer.example.com", keyset,
82− &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
80+ verifier = func(ctx context.Context, token string) (interface{}, error) {
81+ return oidc.NewVerifier(
82+ "https://issuer.example.com",
83+ noOpKeySet{},
84+ &oidc.Config{
85+ ClientID: "https://test.myapp.com",
86+ SkipExpiryCheck: true,
87+ },
88+ ).Verify(ctx, token)
89+ }
8390 })
8491
8592 type jwtSessionLoaderTableInput struct {
@@ -167,16 +174,23 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
167174 const nonVerifiedToken = validToken
168175
169176 BeforeEach(func() {
170− keyset := noOpKeySet{}
171− verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
172− &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
177+ verifier := func(ctx context.Context, token string) (interface{}, error) {
178+ return oidc.NewVerifier(
179+ "https://issuer.example.com",
180+ noOpKeySet{},
181+ &oidc.Config{
182+ ClientID: "https://test.myapp.com",
183+ SkipExpiryCheck: true,
184+ },
185+ ).Verify(ctx, token)
186+ }
173187
174188 j = &jwtSessionLoader{
175189 jwtRegex: regexp.MustCompile(jwtRegexFormat),
176190 sessionLoaders: []middlewareapi.TokenToSessionLoader{
177191 {
178192 Verifier: verifier,
179− TokenToSession: createSessionStateFromBearerToken,
193+ TokenToSession: createSessionFromToken,
180194 },
181195 },
182196 }
@@ -239,7 +253,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
239253 )
240254 })
241255
242− Context("findBearerTokenFromHeader", func() {
256+ Context("findTokenFromHeader", func() {
243257 var j *jwtSessionLoader
244258
245259 BeforeEach(func() {
@@ -256,7 +270,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
256270
257271 DescribeTable("with a header",
258272 func(in findBearerTokenFromHeaderTableInput) {
259− token, err := j.findBearerTokenFromHeader(in.header)
273+ token, err := j.findTokenFromHeader(in.header)
260274 if in.expectedErr != nil {
261275 Expect(err).To(MatchError(in.expectedErr))
262276 } else {
@@ -381,7 +395,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
381395 )
382396 })
383397
384− Context("createSessionStateFromBearerToken", func() {
398+ Context("createSessionFromToken", func() {
385399 ctx := context.Background()
386400 expiresFuture := time.Now().Add(time.Duration(5) * time.Minute)
387401 verified := true
@@ -403,11 +417,18 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
403417
404418 DescribeTable("when creating a session from an IDToken",
405419 func(in createSessionStateTableInput) {
406− verifier := oidc.NewVerifier(
407− "https://issuer.example.com",
408− noOpKeySet{},
409− &oidc.Config{ClientID: "asdf1234"},
410− )
420+ verifier := func(ctx context.Context, token string) (interface{}, error) {
421+ oidcVerifier := oidc.NewVerifier(
422+ "https://issuer.example.com",
423+ noOpKeySet{},
424+ &oidc.Config{ClientID: "asdf1234"},
425+ )
426+
427+ idToken, err := oidcVerifier.Verify(ctx, token)
428+ Expect(err).ToNot(HaveOccurred())
429+
430+ return idToken, nil
431+ }
411432
412433 key, err := rsa.GenerateKey(rand.Reader, 2048)
413434 Expect(err).ToNot(HaveOccurred())
@@ -415,11 +436,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
415436 rawIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS256, in.idToken).SignedString(key)
416437 Expect(err).ToNot(HaveOccurred())
417438
418− // Pass to a dummy Verifier to get an oidc.IDToken from the rawIDToken for our actual test below
419− idToken, err := verifier.Verify(context.Background(), rawIDToken)
420− Expect(err).ToNot(HaveOccurred())
421−
422− session, err := createSessionStateFromBearerToken(ctx, rawIDToken, idToken)
439+ session, err := createSessionFromToken(ctx, rawIDToken, verifier)
423440 if in.expectedErr != nil {
424441 Expect(err).To(MatchError(in.expectedErr))
425442 Expect(session).To(BeNil())
providers/oidc.go+14−3
@@ -11,6 +11,7 @@ import (
1111 oidc "github.com/coreos/go-oidc"
1212 "golang.org/x/oauth2"
1313
14+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1415 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1516 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1617 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@@ -175,14 +176,24 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
175176 return newSession, nil
176177}
177178
178−func (p *OIDCProvider) CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) {
179+func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) {
180+ verifiedToken, err := verify(ctx, token)
181+ if err != nil {
182+ return nil, err
183+ }
184+
185+ idToken, ok := verifiedToken.(*oidc.IDToken)
186+ if !ok {
187+ return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token)
188+ }
189+
179190 newSession, err := p.createSessionStateInternal(ctx, idToken, nil)
180191 if err != nil {
181192 return nil, err
182193 }
183194
184− newSession.AccessToken = rawIDToken
185− newSession.IDToken = rawIDToken
195+ newSession.AccessToken = token
196+ newSession.IDToken = token
186197 newSession.RefreshToken = ""
187198 newSession.ExpiresOn = &idToken.Expiry
188199
providers/oidc_test.go+10−6
@@ -347,14 +347,18 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
347347 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
348348 assert.NoError(t, err)
349349
350− keyset := fakeKeySetStub{}
351− verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
352− &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
350+ verifyFunc := func(ctx context.Context, token string) (interface{}, error) {
351+ keyset := fakeKeySetStub{}
352+ verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
353+ &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
353354
354− idToken, err := verifier.Verify(context.Background(), rawIDToken)
355− assert.NoError(t, err)
355+ idToken, err := verifier.Verify(ctx, token)
356+ assert.NoError(t, err)
357+
358+ return idToken, nil
359+ }
356360
357− ss, err := provider.CreateSessionFromBearer(context.Background(), rawIDToken, idToken)
361+ ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken, verifyFunc)
358362 assert.NoError(t, err)
359363
360364 assert.Equal(t, tc.ExpectedUser, ss.User)
providers/provider_default.go+2−3
@@ -8,8 +8,7 @@ import (
88 "net/url"
99 "time"
1010
11− "github.com/coreos/go-oidc"
12−
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1312 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1413 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
1514)
@@ -127,6 +126,6 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses
127126
128127// CreateSessionStateFromBearerToken should be implemented to allow providers
129128// to convert ID tokens into sessions
130−func (p *ProviderData) CreateSessionFromBearer(_ context.Context, _ string, _ *oidc.IDToken) (*sessions.SessionState, error) {
129+func (p *ProviderData) CreateSessionFromToken(_ context.Context, _ string, _ middleware.VerifyFunc) (*sessions.SessionState, error) {
131130 return nil, ErrNotImplemented
132131}
providers/providers.go+2−2
@@ -3,7 +3,7 @@ package providers
33import (
44 "context"
55
6− "github.com/coreos/go-oidc"
6+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
77 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
88)
99
@@ -18,7 +18,7 @@ type Provider interface {
1818 ValidateSession(ctx context.Context, s *sessions.SessionState) bool
1919 GetLoginURL(redirectURI, finalRedirect string) string
2020 RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error)
21− CreateSessionFromBearer(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error)
21+ CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error)
2222}
2323
2424// New provides a new Provider based on the configured provider string