@@ -19,12 +19,13 @@ import (
19
19
"github.com/cockroachdb/cockroach/pkg/ccl/jwtauthccl"
20
20
"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
21
21
"github.com/cockroachdb/cockroach/pkg/roachpb"
22
- "github.com/cockroachdb/cockroach/pkg/security/username"
22
+ secuser "github.com/cockroachdb/cockroach/pkg/security/username"
23
23
"github.com/cockroachdb/cockroach/pkg/server"
24
24
"github.com/cockroachdb/cockroach/pkg/server/authserver"
25
25
"github.com/cockroachdb/cockroach/pkg/server/serverpb"
26
26
"github.com/cockroachdb/cockroach/pkg/server/telemetry"
27
27
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
28
+ "github.com/cockroachdb/cockroach/pkg/sql"
28
29
"github.com/cockroachdb/cockroach/pkg/sql/pgwire"
29
30
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/identmap"
30
31
"github.com/cockroachdb/cockroach/pkg/ui"
@@ -34,6 +35,7 @@ import (
34
35
"github.com/cockroachdb/cockroach/pkg/util/uuid"
35
36
"github.com/cockroachdb/errors"
36
37
oidc "github.com/coreos/go-oidc"
38
+ "github.com/lestrrat-go/jwx/v2/jwt"
37
39
"golang.org/x/oauth2"
38
40
)
39
41
@@ -131,6 +133,7 @@ type oidcAuthenticationServer struct {
131
133
// to help us gracefully recover from auth provider downtime without operator intervention.
132
134
enabled bool
133
135
initialized bool
136
+ execCfg * sql.ExecutorConfig
134
137
}
135
138
136
139
type oidcAuthenticationConf struct {
@@ -152,6 +155,9 @@ type oidcAuthenticationConf struct {
152
155
generateJWTAuthTokenSQLPort int64
153
156
providerCustomCA string
154
157
httpClient * httputil.Client
158
+ authZEnabled bool
159
+ groupClaim string
160
+ userinfoGroupKey string
155
161
}
156
162
157
163
// GetOIDCConf is used to extract certain parts of the OIDC
@@ -187,37 +193,107 @@ type oidcManager struct {
187
193
oauth2Config * oauth2.Config
188
194
verifier * oidc.IDTokenVerifier
189
195
httpClient * httputil.Client
196
+ provider * oidc.Provider
190
197
}
191
198
192
- func (o * oidcManager ) ExchangeVerifyGetClaims (
193
- ctx context.Context , code string , idTokenKey string ,
194
- ) (map [string ]json.RawMessage , error ) {
199
+ // ExchangeVerifyGetTokenInfo exchanges the auth-code, verifies the ID-token,
200
+ // and extracts claims from both the ID and Access tokens if they are JWTs.
201
+ // Access tokens are only processed if OIDC authorization is enabled for the
202
+ // cluster.
203
+ func (o * oidcManager ) ExchangeVerifyGetTokenInfo (
204
+ ctx context.Context , code , idTokenKey string , authZEnabled bool ,
205
+ ) (
206
+ idTokenClaims , accessTokenClaims map [string ]json.RawMessage ,
207
+ rawIDToken , rawAccessToken string ,
208
+ err error ,
209
+ ) {
195
210
credentials , err := o .Exchange (ctx , code )
196
211
if err != nil {
197
- log .Errorf (ctx , "OIDC: failed to exchange code for token: %v" , err )
198
- return nil , err
212
+ return nil , nil , "" , "" , errors .Wrap (err , "OIDC: failed to exchange code for token" )
199
213
}
200
214
201
- rawIDToken , ok := credentials .Extra (idTokenKey ).(string )
202
- if ! ok {
203
- err := errors .New ("OIDC: failed to extract ID token from the token credentials" )
204
- log .Error (ctx , "OIDC: failed to extract ID token from the token credentials" )
205
- return nil , err
215
+ // Build the list of tokens we must handle. For the ID token we verify
216
+ // signature and claims up-front; the access token is copied as-is.
217
+ // Since we use the OIDC Authorization Code flow (response_type=code),
218
+ // The token endpoint response must contain both id_token and access_token
219
+ // (refresh_token is optional). Their presence is therefore guaranteed here.
220
+ tokensToProcess := []struct {
221
+ name string
222
+ getVerifiedToken func () (string , error ) // fetches the raw token; verifies when needed
223
+ claims * map [string ]json.RawMessage // destination for parsed claims
224
+ rawToken * string // destination for the raw token string
225
+ required bool
226
+ }{
227
+ {
228
+ name : idTokenKey ,
229
+ required : true ,
230
+ getVerifiedToken : func () (string , error ) {
231
+ val , _ := credentials .Extra (idTokenKey ).(string )
232
+ if val == "" {
233
+ log .Ops .Warning (ctx , "OIDC: required id_token missing from credentials" )
234
+ return "" , errors .New ("OIDC: required id_token missing from credentials" )
235
+ }
236
+ // The ID token must be verified against the provider.
237
+ if _ , err := o .Verify (ctx , val ); err != nil {
238
+ return "" , err
239
+ }
240
+ return val , nil
241
+ },
242
+ claims : & idTokenClaims ,
243
+ rawToken : & rawIDToken ,
244
+ },
245
+ {
246
+ name : "access_token" ,
247
+ required : authZEnabled ,
248
+ getVerifiedToken : func () (string , error ) {
249
+ val := credentials .AccessToken
250
+ if val == "" {
251
+ log .Ops .Warning (ctx , "OIDC: required access_token missing from credentials" )
252
+ return "" , errors .New ("OIDC: required access_token missing from credentials" )
253
+ }
254
+ return val , nil
255
+ },
256
+ claims : & accessTokenClaims ,
257
+ rawToken : & rawAccessToken ,
258
+ },
206
259
}
207
260
208
- idToken , err := o .Verify (ctx , rawIDToken )
209
- if err != nil {
210
- log .Errorf (ctx , "OIDC: unable to verify ID token: %v" , err )
211
- return nil , err
212
- }
261
+ for _ , tokenInfo := range tokensToProcess {
262
+ if ! tokenInfo .required {
263
+ continue
264
+ }
265
+ var err error
266
+ * tokenInfo .rawToken , err = tokenInfo .getVerifiedToken ()
267
+ if err != nil {
268
+ return nil , nil , "" , "" , errors .Wrapf (err , "OIDC: failed to verify %s" , tokenInfo .name )
269
+ }
270
+ // Attempt to parse the token as a JWT to extract its claims.
271
+ // We keep this at INFO(1) because opaque tokens are normal for many IdPs;
272
+ // the message is useful for troubleshooting but not an operator-actionable error.
273
+ parsedToken , err := jwt .ParseInsecure ([]byte (* tokenInfo .rawToken ))
274
+ if err != nil {
275
+ log .VInfof (ctx , 1 , "OIDC: could not parse %s as JWT (this is expected for opaque tokens): %v" , tokenInfo .name , err )
276
+ continue // Not a JWT, so we can't get claims from it.
277
+ }
213
278
214
- var claims map [string ]json.RawMessage
215
- if err := idToken .Claims (& claims ); err != nil {
216
- log .Errorf (ctx , "OIDC: unable to deserialize token claims: %v" , err )
217
- return nil , err
279
+ // Convert the jwt.Token into a map to extract all claims.
280
+ claimsMap , err := parsedToken .AsMap (ctx )
281
+ if err != nil {
282
+ return nil , nil , "" , "" , errors .Wrapf (err , "OIDC: failed to get claims as map from %s" , tokenInfo .name )
283
+ }
284
+ // The claims map must be marshaled and unmarshaled to convert it to the desired type.
285
+ jsonBytes , err := json .Marshal (claimsMap )
286
+ if err != nil {
287
+ return nil , nil , "" , "" , errors .Wrapf (err , "OIDC: failed to marshal claims from %s" , tokenInfo .name )
288
+ }
289
+ var claimsData map [string ]json.RawMessage
290
+ if err := json .Unmarshal (jsonBytes , & claimsData ); err != nil {
291
+ return nil , nil , "" , "" , errors .Wrapf (err , "OIDC: failed to deserialize claims from %s" , tokenInfo .name )
292
+ }
293
+ * tokenInfo .claims = claimsData
218
294
}
219
295
220
- return claims , nil
296
+ return idTokenClaims , accessTokenClaims , rawIDToken , rawAccessToken , nil
221
297
}
222
298
223
299
func (o * oidcManager ) Verify (ctx context.Context , s string ) (* oidc.IDToken , error ) {
@@ -247,11 +323,17 @@ func (o oidcManager) AuthCodeURL(s string, option ...oauth2.AuthCodeOption) stri
247
323
return o .oauth2Config .AuthCodeURL (s , option ... )
248
324
}
249
325
326
+ func (o * oidcManager ) UserInfo (ctx context.Context , ts oauth2.TokenSource ) (* oidc.UserInfo , error ) {
327
+ octx := oidc .ClientContext (ctx , o .httpClient .Client )
328
+ return o .provider .UserInfo (octx , ts )
329
+ }
330
+
250
331
type IOIDCManager interface {
251
332
Verify (context.Context , string ) (* oidc.IDToken , error )
252
333
Exchange (context.Context , string , ... oauth2.AuthCodeOption ) (* oauth2.Token , error )
253
334
AuthCodeURL (string , ... oauth2.AuthCodeOption ) string
254
- ExchangeVerifyGetClaims (context.Context , string , string ) (map [string ]json.RawMessage , error )
335
+ ExchangeVerifyGetTokenInfo (context.Context , string , string , bool ) (idTokenClaims , accessTokenClaims map [string ]json.RawMessage , rawIDToken , rawAccessToken string , err error )
336
+ UserInfo (context.Context , oauth2.TokenSource ) (* oidc.UserInfo , error )
255
337
}
256
338
257
339
var _ IOIDCManager = & oidcManager {}
@@ -291,6 +373,7 @@ var NewOIDCManager func(context.Context, oidcAuthenticationConf, string, []strin
291
373
verifier : verifier ,
292
374
oauth2Config : oauth2Config ,
293
375
httpClient : conf .httpClient ,
376
+ provider : provider ,
294
377
}, nil
295
378
}
296
379
@@ -337,6 +420,9 @@ func reloadConfigLocked(
337
420
httputil .WithDialerTimeout (clientTimeout ),
338
421
httputil .WithCustomCAPEM (OIDCProviderCustomCA .Get (& st .SV )),
339
422
),
423
+ authZEnabled : OIDCAuthZEnabled .Get (& st .SV ),
424
+ groupClaim : OIDCAuthGroupClaim .Get (& st .SV ),
425
+ userinfoGroupKey : OIDCAuthUserinfoGroupKey .Get (& st .SV ),
340
426
}
341
427
342
428
if ! oidcAuthServer .conf .enabled && conf .enabled {
@@ -421,8 +507,11 @@ var ConfigureOIDC = func(
421
507
userLoginFromSSO func (ctx context.Context , username string ) (* http.Cookie , error ),
422
508
ambientCtx log.AmbientContext ,
423
509
cluster uuid.UUID ,
510
+ execCfg * sql.ExecutorConfig ,
424
511
) (authserver.OIDC , error ) {
425
- oidcAuthentication := & oidcAuthenticationServer {}
512
+ oidcAuthentication := & oidcAuthenticationServer {
513
+ execCfg : execCfg ,
514
+ }
426
515
427
516
// Don't want to use GRPC here since these endpoints require HTTP-Redirect behaviors and the
428
517
// callback endpoint will be receiving specialized parameters that grpc-gateway will only get
@@ -493,8 +582,11 @@ var ConfigureOIDC = func(
493
582
return
494
583
}
495
584
496
- claims , err := oidcAuthentication .manager .ExchangeVerifyGetClaims (ctx , r .URL .Query ().Get (codeKey ), idTokenKey )
585
+ idTokenClaims , _ , rawIDToken , rawAccessToken , err := oidcAuthentication .manager .
586
+ ExchangeVerifyGetTokenInfo (ctx , r .URL .Query ().Get (codeKey ), idTokenKey , oidcAuthentication .conf .authZEnabled )
587
+
497
588
if err != nil {
589
+ log .Errorf (ctx , "OIDC: failed to get and verify token: %v" , err )
498
590
http .Error (w , genericCallbackHTTPError , http .StatusInternalServerError )
499
591
return
500
592
}
@@ -509,13 +601,20 @@ var ConfigureOIDC = func(
509
601
}
510
602
511
603
username , err := extractUsernameFromClaims (
512
- ctx , claims , oidcAuthentication .conf .claimJSONKey , oidcAuthentication .conf .principalRegex ,
604
+ ctx , idTokenClaims , oidcAuthentication .conf .claimJSONKey , oidcAuthentication .conf .principalRegex ,
513
605
)
514
606
if err != nil {
515
607
http .Error (w , genericCallbackHTTPError , http .StatusInternalServerError )
516
608
return
517
609
}
518
610
611
+ // OIDC authorization
612
+ if err := oidcAuthentication .authorize (ctx , rawIDToken , rawAccessToken , username ); err != nil {
613
+ log .Errorf (ctx , "OIDC authorization failed with error: %v" , err )
614
+ http .Error (w , genericCallbackHTTPError , http .StatusForbidden )
615
+ return
616
+ }
617
+
519
618
cookie , err := userLoginFromSSO (ctx , username )
520
619
if err != nil {
521
620
log .Errorf (ctx , "OIDC: failed to complete authentication: unable to create session for %s: %v" , username , err )
@@ -705,7 +804,7 @@ var ConfigureOIDC = func(
705
804
}
706
805
} else {
707
806
log .Infof (ctx , "OIDC: no identity map found for issuer %s; using %s without mapping" , token .Issuer , tokenPrincipal )
708
- if username , err := username .MakeSQLUsernameFromUserInput (tokenPrincipal , username .PurposeValidation ); err != nil {
807
+ if username , err := secuser .MakeSQLUsernameFromUserInput (tokenPrincipal , secuser .PurposeValidation ); err != nil {
709
808
acceptedUsernames = append (acceptedUsernames , username .Normalized ())
710
809
}
711
810
}
@@ -829,6 +928,15 @@ var ConfigureOIDC = func(
829
928
OIDCAuthClientTimeout .SetOnChange (& st .SV , func (ctx context.Context ) {
830
929
reloadConfig (ambientCtx .AnnotateCtx (ctx ), oidcAuthentication , locality , st )
831
930
})
931
+ OIDCAuthZEnabled .SetOnChange (& st .SV , func (ctx context.Context ) {
932
+ reloadConfig (ambientCtx .AnnotateCtx (ctx ), oidcAuthentication , locality , st )
933
+ })
934
+ OIDCAuthGroupClaim .SetOnChange (& st .SV , func (ctx context.Context ) {
935
+ reloadConfig (ambientCtx .AnnotateCtx (ctx ), oidcAuthentication , locality , st )
936
+ })
937
+ OIDCAuthUserinfoGroupKey .SetOnChange (& st .SV , func (ctx context.Context ) {
938
+ reloadConfig (ambientCtx .AnnotateCtx (ctx ), oidcAuthentication , locality , st )
939
+ })
832
940
833
941
return oidcAuthentication , nil
834
942
}
0 commit comments