Skip to content

Commit c4f6da8

Browse files
authored
Merge pull request #160016 from souravcrl/blathers/backport-release-26.1-159787
release-26.1: oidcccl,provisioning: add user provisioning for OIDC authentication
2 parents 3e5562e + 3e93151 commit c4f6da8

File tree

7 files changed

+633
-11
lines changed

7 files changed

+633
-11
lines changed

pkg/ccl/logictestccl/testdata/logic_test/provisioning

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
statement error role "root" cannot have a PROVISIONSRC
44
ALTER ROLE root PROVISIONSRC 'ldap:ldap.example.com'
55

6-
statement error pq: PROVISIONSRC "ldap.example.com" was not prefixed with any valid auth methods \["ldap" "jwt_token"\]
6+
statement error pq: PROVISIONSRC "ldap.example.com" was not prefixed with any valid auth methods \["ldap" "jwt_token" "oidc"\]
77
CREATE ROLE role_with_provisioning PROVISIONSRC 'ldap.example.com'
88

99
statement error pq: conflicting role options

pkg/ccl/oidcccl/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ go_library(
1616
"//pkg/ccl/securityccl/jwthelper",
1717
"//pkg/ccl/utilccl",
1818
"//pkg/roachpb",
19+
"//pkg/security/provisioning",
1920
"//pkg/security/username",
2021
"//pkg/server",
2122
"//pkg/server/authserver",
@@ -55,6 +56,7 @@ go_test(
5556
"//pkg/ccl",
5657
"//pkg/roachpb",
5758
"//pkg/security/certnames",
59+
"//pkg/security/provisioning",
5860
"//pkg/security/securityassets",
5961
"//pkg/security/securitytest",
6062
"//pkg/security/username",

pkg/ccl/oidcccl/authentication_oidc.go

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/cockroachdb/cockroach/pkg/ccl/jwtauthccl"
2020
"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
2121
"github.com/cockroachdb/cockroach/pkg/roachpb"
22+
"github.com/cockroachdb/cockroach/pkg/security/provisioning"
2223
secuser "github.com/cockroachdb/cockroach/pkg/security/username"
2324
"github.com/cockroachdb/cockroach/pkg/server"
2425
"github.com/cockroachdb/cockroach/pkg/server/authserver"
@@ -44,6 +45,7 @@ const (
4445
codeKey = "code"
4546
stateKey = "state"
4647
secretCookieName = "oidc_secret"
48+
oidcProvisioningKey = "oidc"
4749
oidcLoginPath = "/oidc/v1/login"
4850
oidcCallbackPath = "/oidc/v1/callback"
4951
oidcJWTPath = "/oidc/v1/jwt"
@@ -158,6 +160,7 @@ type oidcAuthenticationConf struct {
158160
authZEnabled bool
159161
groupClaim string
160162
userinfoGroupKey string
163+
provisioningEnabled bool
161164
}
162165

163166
// GetOIDCConf is used to extract certain parts of the OIDC
@@ -420,9 +423,10 @@ func reloadConfigLocked(
420423
httputil.WithDialerTimeout(clientTimeout),
421424
httputil.WithCustomCAPEM(OIDCProviderCustomCA.Get(&st.SV)),
422425
),
423-
authZEnabled: OIDCAuthZEnabled.Get(&st.SV),
424-
groupClaim: OIDCAuthGroupClaim.Get(&st.SV),
425-
userinfoGroupKey: OIDCAuthUserinfoGroupKey.Get(&st.SV),
426+
authZEnabled: OIDCAuthZEnabled.Get(&st.SV),
427+
groupClaim: OIDCAuthGroupClaim.Get(&st.SV),
428+
userinfoGroupKey: OIDCAuthUserinfoGroupKey.Get(&st.SV),
429+
provisioningEnabled: provisioning.ClusterProvisioningConfig(st).Enabled("oidc"),
426430
}
427431

428432
if !oidcAuthServer.conf.enabled && conf.enabled {
@@ -490,6 +494,69 @@ func getRegionSpecificRedirectURL(locality roachpb.Locality, conf redirectURLCon
490494
return s, nil
491495
}
492496

497+
// maybeProvisionUserLocked checks the cached OIDC configuration to see whether
498+
// automatic user provisioning is enabled. If so, it attempts to create a SQL
499+
// user linked to the OIDC identity provider.
500+
//
501+
// This function is called after a successful OIDC token exchange and
502+
// verification. Its execution relies on the success of the underlying OIDC
503+
// library, which operates on the following assumptions:
504+
//
505+
// 1. OIDC Discovery: The library uses the OIDC discovery protocol to fetch the
506+
// provider's configuration from "/.well-known/openid-configuration". This
507+
// assumes the provider has discovery enabled and accessible.
508+
//
509+
// 2. Issuer Validation: The go-oidc library's verifier ensures the 'iss' claim
510+
// in the ID Token matches the issuer URL from the discovery document. There
511+
// is also an exception made to this in go-oidc for accounts.google.com
512+
//
513+
// Errors during username validation, provisioning source parsing, or user creation
514+
// are logged with detailed context and result in an HTTP 500 response with a
515+
// generic error message to the client.
516+
func maybeProvisionUserLocked(
517+
ctx context.Context,
518+
authConf oidcAuthenticationConf,
519+
execCfg *sql.ExecutorConfig,
520+
username string,
521+
w http.ResponseWriter,
522+
) (err error) {
523+
if !authConf.provisioningEnabled {
524+
return
525+
}
526+
527+
log.Dev.Infof(ctx, "OIDC: attempting user provisioning for %s", username)
528+
telemetry.Inc(provisioning.BeginOIDCProvisionUseCounter)
529+
530+
// Convert the extracted username string to a username.SQLUsername type.
531+
sqlUsername, err := secuser.MakeSQLUsernameFromUserInput(username, secuser.PurposeCreation)
532+
if err != nil {
533+
log.Dev.Errorf(ctx, "OIDC provisioning: invalid username format for %s: %v", username, err)
534+
http.Error(w, "OIDC: invalid username format", http.StatusInternalServerError)
535+
return err
536+
}
537+
538+
// Create the provisioning source identifier string, e.g., "oidc:https://accounts.example.com".
539+
idpString := oidcProvisioningKey + ":" + authConf.providerURL
540+
provisioningSource, err := provisioning.ParseProvisioningSource(idpString)
541+
if err != nil {
542+
// This error occurs if the provisioning package doesn't recognize the "oidc:" prefix.
543+
log.Dev.Errorf(ctx, "OIDC provisioning: error parsing provisioning source IDP %s: %v", idpString, err)
544+
http.Error(w, "OIDC: provisioning error", http.StatusInternalServerError)
545+
return err
546+
}
547+
548+
// Call the core provisioning function using the execCfg.
549+
if err = sql.CreateRoleForProvisioning(ctx, execCfg, sqlUsername, provisioningSource.String()); err != nil {
550+
log.Dev.Errorf(ctx, "OIDC provisioning: error provisioning user %s: %v", sqlUsername, err)
551+
http.Error(w, "OIDC: provisioning error", http.StatusInternalServerError)
552+
return err
553+
}
554+
555+
log.Dev.Infof(ctx, "OIDC: successfully provisioned user %s", sqlUsername)
556+
telemetry.Inc(provisioning.ProvisionOIDCSuccessCounter)
557+
return
558+
}
559+
493560
// ConfigureOIDC attaches handlers to the server `mux` that
494561
// can initiate and complete an OIDC authentication flow.
495562
// This flow consists of an initial login request that triggers
@@ -608,6 +675,12 @@ var ConfigureOIDC = func(
608675
return
609676
}
610677

678+
// OIDC user provisioning
679+
if err := maybeProvisionUserLocked(ctx, oidcAuthentication.conf, oidcAuthentication.execCfg, username, w); err != nil {
680+
log.Dev.Errorf(ctx, "OIDC provisioning failed with error: %v", err)
681+
return
682+
}
683+
611684
// OIDC authorization
612685
if err := oidcAuthentication.authorize(ctx, rawIDToken, rawAccessToken, username); err != nil {
613686
log.Dev.Errorf(ctx, "OIDC authorization failed with error: %v", err)
@@ -938,6 +1011,9 @@ var ConfigureOIDC = func(
9381011
OIDCAuthUserinfoGroupKey.SetOnChange(&st.SV, func(ctx context.Context) {
9391012
reloadConfig(ambientCtx.AnnotateCtx(ctx), oidcAuthentication, locality, st)
9401013
})
1014+
provisioning.OIDCProvisioningEnabled.SetOnChange(&st.SV, func(ctx context.Context) {
1015+
reloadConfig(ambientCtx.AnnotateCtx(ctx), oidcAuthentication, locality, st)
1016+
})
9411017

9421018
return oidcAuthentication, nil
9431019
}

0 commit comments

Comments
 (0)