mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 13:37:57 +08:00
Add OIDC workload identity authentication support
- Add BearerOIDCConfig to configuration for OIDC workload auth - Implement OIDCBearerAuthorizer for validating OIDC ID tokens - Update bearerAuthHandler to support both traditional and OIDC bearer auth - Add claim mapping support for extracting username from OIDC tokens - Support multiple audiences for token validation - Extract groups from token claims for authorization Co-authored-by: rchincha <45800463+rchincha@users.noreply.github.com>
This commit is contained in:
+81
-23
@@ -485,18 +485,34 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
// Get auth config safely
|
||||
authConfig := ctlr.Config.CopyAuthConfig()
|
||||
|
||||
// although the configuration option is called 'cert', this function will also parse a public key directly
|
||||
// see https://github.com/project-zot/zot/issues/3173 for info
|
||||
publicKey, err := loadPublicKeyFromFile(authConfig.Bearer.Cert)
|
||||
if err != nil {
|
||||
ctlr.Log.Panic().Err(err).Msg("failed to load public key for bearer authentication")
|
||||
// Initialize authorizers based on configuration
|
||||
var traditionalAuthorizer *BearerAuthorizer
|
||||
var oidcAuthorizer *OIDCBearerAuthorizer
|
||||
|
||||
// Traditional bearer auth with public key/certificate
|
||||
if authConfig.Bearer.Cert != "" {
|
||||
// although the configuration option is called 'cert', this function will also parse a public key directly
|
||||
// see https://github.com/project-zot/zot/issues/3173 for info
|
||||
publicKey, err := loadPublicKeyFromFile(authConfig.Bearer.Cert)
|
||||
if err != nil {
|
||||
ctlr.Log.Panic().Err(err).Msg("failed to load public key for bearer authentication")
|
||||
}
|
||||
|
||||
traditionalAuthorizer = &BearerAuthorizer{
|
||||
realm: authConfig.Bearer.Realm,
|
||||
service: authConfig.Bearer.Service,
|
||||
key: publicKey,
|
||||
}
|
||||
}
|
||||
|
||||
authorizer := NewBearerAuthorizer(
|
||||
authConfig.Bearer.Realm,
|
||||
authConfig.Bearer.Service,
|
||||
publicKey,
|
||||
)
|
||||
// OIDC bearer auth for workload identity
|
||||
if authConfig.Bearer.OIDC != nil {
|
||||
var err error
|
||||
oidcAuthorizer, err = NewOIDCBearerAuthorizer(context.Background(), authConfig.Bearer.OIDC, ctlr.Log)
|
||||
if err != nil {
|
||||
ctlr.Log.Panic().Err(err).Msg("failed to initialize OIDC bearer authorizer")
|
||||
}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
@@ -548,27 +564,69 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
err := authorizer.Authorize(header, requestedAccess)
|
||||
if err != nil {
|
||||
var challenge *AuthChallengeError
|
||||
if errors.As(err, &challenge) {
|
||||
ctlr.Log.Debug().Err(challenge).Msg("bearer token authorization failed")
|
||||
// Try OIDC authentication first if configured
|
||||
authenticated := false
|
||||
var username string
|
||||
var groups []string
|
||||
|
||||
if oidcAuthorizer != nil {
|
||||
var err error
|
||||
username, groups, authenticated, err = oidcAuthorizer.AuthenticateRequest(request.Context(), header)
|
||||
if err == nil && authenticated {
|
||||
// OIDC authentication succeeded
|
||||
ctlr.Log.Debug().Str("username", username).Msg("OIDC bearer authentication successful")
|
||||
|
||||
// Set user context for authorization
|
||||
userAc := reqCtx.NewUserAccessControl()
|
||||
userAc.SetUsername(username)
|
||||
userAc.AddGroups(groups)
|
||||
userAc.SaveOnRequest(request)
|
||||
|
||||
// Update user groups in MetaDB if available
|
||||
if ctlr.MetaDB != nil {
|
||||
if err := ctlr.MetaDB.SetUserGroups(request.Context(), groups); err != nil {
|
||||
ctlr.Log.Error().Err(err).Str("username", username).Msg("failed to update user profile")
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
amCtx := acCtrlr.getAuthnMiddlewareContext(BEARER, request)
|
||||
next.ServeHTTP(response, request.WithContext(amCtx)) //nolint:contextcheck
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to traditional bearer token auth if OIDC didn't succeed
|
||||
if traditionalAuthorizer != nil {
|
||||
err := traditionalAuthorizer.Authorize(header, requestedAccess)
|
||||
if err != nil {
|
||||
var challenge *AuthChallengeError
|
||||
if errors.As(err, &challenge) {
|
||||
ctlr.Log.Debug().Err(challenge).Msg("bearer token authorization failed")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Header().Set("WWW-Authenticate", challenge.Header())
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Header().Set("WWW-Authenticate", challenge.Header())
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
|
||||
|
||||
amCtx := acCtrlr.getAuthnMiddlewareContext(BEARER, request)
|
||||
next.ServeHTTP(response, request.WithContext(amCtx)) //nolint:contextcheck
|
||||
return
|
||||
}
|
||||
|
||||
amCtx := acCtrlr.getAuthnMiddlewareContext(BEARER, request)
|
||||
next.ServeHTTP(response, request.WithContext(amCtx)) //nolint:contextcheck
|
||||
// No authentication succeeded
|
||||
ctlr.Log.Error().Msg("bearer authentication failed")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user