Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/ccl/logictestccl/testdata/logic_test/provisioning
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
statement error role "root" cannot have a PROVISIONSRC
ALTER ROLE root PROVISIONSRC 'ldap:ldap.example.com'

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

statement error pq: conflicting role options
Expand Down
2 changes: 2 additions & 0 deletions pkg/ccl/oidcccl/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go_library(
"//pkg/ccl/securityccl/jwthelper",
"//pkg/ccl/utilccl",
"//pkg/roachpb",
"//pkg/security/provisioning",
"//pkg/security/username",
"//pkg/server",
"//pkg/server/authserver",
Expand Down Expand Up @@ -55,6 +56,7 @@ go_test(
"//pkg/ccl",
"//pkg/roachpb",
"//pkg/security/certnames",
"//pkg/security/provisioning",
"//pkg/security/securityassets",
"//pkg/security/securitytest",
"//pkg/security/username",
Expand Down
82 changes: 79 additions & 3 deletions pkg/ccl/oidcccl/authentication_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/ccl/jwtauthccl"
"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security/provisioning"
secuser "github.com/cockroachdb/cockroach/pkg/security/username"
"github.com/cockroachdb/cockroach/pkg/server"
"github.com/cockroachdb/cockroach/pkg/server/authserver"
Expand All @@ -44,6 +45,7 @@ const (
codeKey = "code"
stateKey = "state"
secretCookieName = "oidc_secret"
oidcProvisioningKey = "oidc"
oidcLoginPath = "/oidc/v1/login"
oidcCallbackPath = "/oidc/v1/callback"
oidcJWTPath = "/oidc/v1/jwt"
Expand Down Expand Up @@ -158,6 +160,7 @@ type oidcAuthenticationConf struct {
authZEnabled bool
groupClaim string
userinfoGroupKey string
provisioningEnabled bool
}

// GetOIDCConf is used to extract certain parts of the OIDC
Expand Down Expand Up @@ -420,9 +423,10 @@ func reloadConfigLocked(
httputil.WithDialerTimeout(clientTimeout),
httputil.WithCustomCAPEM(OIDCProviderCustomCA.Get(&st.SV)),
),
authZEnabled: OIDCAuthZEnabled.Get(&st.SV),
groupClaim: OIDCAuthGroupClaim.Get(&st.SV),
userinfoGroupKey: OIDCAuthUserinfoGroupKey.Get(&st.SV),
authZEnabled: OIDCAuthZEnabled.Get(&st.SV),
groupClaim: OIDCAuthGroupClaim.Get(&st.SV),
userinfoGroupKey: OIDCAuthUserinfoGroupKey.Get(&st.SV),
provisioningEnabled: provisioning.ClusterProvisioningConfig(st).Enabled("oidc"),
}

if !oidcAuthServer.conf.enabled && conf.enabled {
Expand Down Expand Up @@ -490,6 +494,69 @@ func getRegionSpecificRedirectURL(locality roachpb.Locality, conf redirectURLCon
return s, nil
}

// maybeProvisionUserLocked checks the cached OIDC configuration to see whether
// automatic user provisioning is enabled. If so, it attempts to create a SQL
// user linked to the OIDC identity provider.
//
// This function is called after a successful OIDC token exchange and
// verification. Its execution relies on the success of the underlying OIDC
// library, which operates on the following assumptions:
//
// 1. OIDC Discovery: The library uses the OIDC discovery protocol to fetch the
// provider's configuration from "/.well-known/openid-configuration". This
// assumes the provider has discovery enabled and accessible.
//
// 2. Issuer Validation: The go-oidc library's verifier ensures the 'iss' claim
// in the ID Token matches the issuer URL from the discovery document. There
// is also an exception made to this in go-oidc for accounts.google.com
//
// Errors during username validation, provisioning source parsing, or user creation
// are logged with detailed context and result in an HTTP 500 response with a
// generic error message to the client.
func maybeProvisionUserLocked(
ctx context.Context,
authConf oidcAuthenticationConf,
execCfg *sql.ExecutorConfig,
username string,
w http.ResponseWriter,
) (err error) {
if !authConf.provisioningEnabled {
return
}

log.Dev.Infof(ctx, "OIDC: attempting user provisioning for %s", username)
telemetry.Inc(provisioning.BeginOIDCProvisionUseCounter)

// Convert the extracted username string to a username.SQLUsername type.
sqlUsername, err := secuser.MakeSQLUsernameFromUserInput(username, secuser.PurposeCreation)
if err != nil {
log.Dev.Errorf(ctx, "OIDC provisioning: invalid username format for %s: %v", username, err)
http.Error(w, "OIDC: invalid username format", http.StatusInternalServerError)
return err
}

// Create the provisioning source identifier string, e.g., "oidc:https://accounts.example.com".
idpString := oidcProvisioningKey + ":" + authConf.providerURL
provisioningSource, err := provisioning.ParseProvisioningSource(idpString)
if err != nil {
// This error occurs if the provisioning package doesn't recognize the "oidc:" prefix.
log.Dev.Errorf(ctx, "OIDC provisioning: error parsing provisioning source IDP %s: %v", idpString, err)
http.Error(w, "OIDC: provisioning error", http.StatusInternalServerError)
return err
}

// Call the core provisioning function using the execCfg.
if err = sql.CreateRoleForProvisioning(ctx, execCfg, sqlUsername, provisioningSource.String()); err != nil {
log.Dev.Errorf(ctx, "OIDC provisioning: error provisioning user %s: %v", sqlUsername, err)
http.Error(w, "OIDC: provisioning error", http.StatusInternalServerError)
return err
}

log.Dev.Infof(ctx, "OIDC: successfully provisioned user %s", sqlUsername)
telemetry.Inc(provisioning.ProvisionOIDCSuccessCounter)
return
}

// ConfigureOIDC attaches handlers to the server `mux` that
// can initiate and complete an OIDC authentication flow.
// This flow consists of an initial login request that triggers
Expand Down Expand Up @@ -608,6 +675,12 @@ var ConfigureOIDC = func(
return
}

// OIDC user provisioning
if err := maybeProvisionUserLocked(ctx, oidcAuthentication.conf, oidcAuthentication.execCfg, username, w); err != nil {
log.Dev.Errorf(ctx, "OIDC provisioning failed with error: %v", err)
return
}

// OIDC authorization
if err := oidcAuthentication.authorize(ctx, rawIDToken, rawAccessToken, username); err != nil {
log.Dev.Errorf(ctx, "OIDC authorization failed with error: %v", err)
Expand Down Expand Up @@ -938,6 +1011,9 @@ var ConfigureOIDC = func(
OIDCAuthUserinfoGroupKey.SetOnChange(&st.SV, func(ctx context.Context) {
reloadConfig(ambientCtx.AnnotateCtx(ctx), oidcAuthentication, locality, st)
})
provisioning.OIDCProvisioningEnabled.SetOnChange(&st.SV, func(ctx context.Context) {
reloadConfig(ambientCtx.AnnotateCtx(ctx), oidcAuthentication, locality, st)
})

return oidcAuthentication, nil
}
Expand Down
Loading