mirror of
https://github.com/project-zot/zot.git
synced 2026-06-19 05:57:57 +08:00
feat(metrics): anonymous access when enabled in accessControl config (#4110)
* feat: add anonymouspolicy support in metrics Signed-off-by: uaggarwa <uaggarwa@akamai.com> * test: add unit tests Signed-off-by: uaggarwa <uaggarwa@akamai.com> --------- Signed-off-by: uaggarwa <uaggarwa@akamai.com>
This commit is contained in:
+30
-3
@@ -40,6 +40,7 @@ import (
|
||||
"zotregistry.dev/zot/v2/pkg/api/constants"
|
||||
apiErr "zotregistry.dev/zot/v2/pkg/api/errors"
|
||||
zcommon "zotregistry.dev/zot/v2/pkg/common"
|
||||
extconf "zotregistry.dev/zot/v2/pkg/extensions/config"
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
reqCtx "zotregistry.dev/zot/v2/pkg/requestcontext"
|
||||
)
|
||||
@@ -432,6 +433,10 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
|
||||
accessControlConfig := ctlr.Config.CopyAccessControlConfig()
|
||||
allowAnonymous := accessControlConfig != nil && accessControlConfig.AnonymousPolicyExists()
|
||||
|
||||
// Allow anonymous access to the metrics endpoint only if configured.
|
||||
extensionsConfig := ctlr.Config.CopyExtensionsConfig()
|
||||
isMetricsRequestedWithAnonymousAccess := isAnonymousMetricsRequest(request, accessControlConfig, extensionsConfig)
|
||||
|
||||
// build user access control info
|
||||
userAc := reqCtx.NewUserAccessControl()
|
||||
// if it will not be populated by authn handlers, this represents an anonymous user
|
||||
@@ -461,7 +466,7 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
|
||||
// If session authentication fails, but anonymous or management access is allowed,
|
||||
// treat the request as authenticated. This fallback is necessary because the session
|
||||
// header may be present for anonymous or management requests.
|
||||
authenticated = authenticated || allowAnonymous || isMgmtRequested
|
||||
authenticated = authenticated || allowAnonymous || isMgmtRequested || isMetricsRequestedWithAnonymousAccess
|
||||
|
||||
// Try mTLS authentication if client certificates are present
|
||||
case ctlr.Config.IsMTLSAuthEnabled() && request.TLS != nil && len(request.TLS.PeerCertificates) > 0:
|
||||
@@ -471,8 +476,8 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
|
||||
case !authConfig.IsBasicAuthnEnabled() && !ctlr.Config.IsMTLSAuthEnabled():
|
||||
authenticated = true
|
||||
|
||||
// If no credentials provided - check for anonymous / mgmt requests
|
||||
case allowAnonymous || isMgmtRequested:
|
||||
// If no credentials provided - check for anonymous / mgmt / metrics requests
|
||||
case allowAnonymous || isMgmtRequested || isMetricsRequestedWithAnonymousAccess:
|
||||
// Docker workaround: force 401 on /v2/ when anonymous policies coexist with
|
||||
// authenticated-only policies. Otherwise Docker treats 200 on /v2/ as "no auth"
|
||||
// and will not send stored credentials for protected repositories.
|
||||
@@ -582,6 +587,16 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Allow anonymous access to the metrics endpoint only if configured.
|
||||
accessControlConfig := ctlr.Config.CopyAccessControlConfig()
|
||||
extensionsConfig := ctlr.Config.CopyExtensionsConfig()
|
||||
|
||||
if isAnonymousMetricsRequest(request, accessControlConfig, extensionsConfig) {
|
||||
next.ServeHTTP(response, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var requestedAccess *ResourceAction
|
||||
|
||||
if request.RequestURI != "/v2/" {
|
||||
@@ -977,6 +992,18 @@ func authFail(w http.ResponseWriter, r *http.Request, realm string, delay int) {
|
||||
zcommon.WriteJSON(w, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
}
|
||||
|
||||
func isAnonymousMetricsRequest(request *http.Request, accessControlConfig *config.AccessControlConfig,
|
||||
extensionsConfig *extconf.ExtensionConfig,
|
||||
) bool {
|
||||
prometheusConfig := extensionsConfig.GetMetricsPrometheusConfig()
|
||||
|
||||
return isAuthorizationHeaderEmpty(request) &&
|
||||
slices.Contains(accessControlConfig.GetMetrics().AnonymousPolicy, constants.ReadPermission) &&
|
||||
extensionsConfig.IsMetricsEnabled() &&
|
||||
prometheusConfig != nil &&
|
||||
strings.HasPrefix(request.URL.Path, prometheusConfig.Path)
|
||||
}
|
||||
|
||||
func isAuthorizationHeaderEmpty(request *http.Request) bool {
|
||||
header := request.Header.Get("Authorization")
|
||||
|
||||
|
||||
@@ -742,6 +742,12 @@ func MetricsAuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
}
|
||||
|
||||
metricsConfig := accessControlConfig.GetMetrics()
|
||||
if slices.Contains(metricsConfig.AnonymousPolicy, constants.ReadPermission) {
|
||||
next.ServeHTTP(response, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(metricsConfig.Users) == 0 {
|
||||
log := ctlr.Log
|
||||
log.Warn().Msg("auth is enabled but no metrics users in accessControl: /metrics is unaccesible")
|
||||
|
||||
@@ -650,6 +650,22 @@ func (config *AccessControlConfig) GetMetrics() Metrics {
|
||||
return config.Metrics
|
||||
}
|
||||
|
||||
// ContainsOnlyMetricsAnonymousPolicy identifies an access control configuration
|
||||
// that contains only an anonymous policy for metrics.
|
||||
func (config *AccessControlConfig) ContainsOnlyMetricsAnonymousPolicy() bool {
|
||||
if config == nil {
|
||||
return false
|
||||
}
|
||||
admin := config.GetAdminPolicy()
|
||||
if len(admin.Actions)+len(admin.Users)+len(admin.Groups) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(config.GetRepositories()) == 0 &&
|
||||
len(config.GetGroups()) == 0 &&
|
||||
slices.Contains(config.GetMetrics().AnonymousPolicy, "read")
|
||||
}
|
||||
|
||||
// GetGroups safely gets a copy of the groups configuration.
|
||||
func (config *AccessControlConfig) GetGroups() Groups {
|
||||
if config == nil {
|
||||
@@ -739,7 +755,8 @@ type Condition struct {
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
Users []string
|
||||
Users []string
|
||||
AnonymousPolicy []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
||||
Reference in New Issue
Block a user