refactor: split AuthZ mdw in 2 different parts, each for a specific purpose (#1542)

- AuthzHandler has now been split in BaseAuthzHandler and DistSpecAuthzHandler
The former populates context with user specific data needed in most handlers, while
the latter executes access logic specific to distribution-spec handlers.

Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
This commit is contained in:
alexstan12
2023-07-05 19:37:52 +03:00
committed by GitHub
parent 7fee57e7cc
commit 62889c3cb1
3 changed files with 71 additions and 37 deletions
+38 -21
View File
@@ -2,15 +2,12 @@ package api
import (
"context"
"fmt"
"net/http"
"strings"
glob "github.com/bmatcuk/doublestar/v4"
"github.com/gorilla/mux"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/log"
localCtx "zotregistry.io/zot/pkg/requestcontext"
@@ -220,12 +217,13 @@ func (ac *AccessController) isPermitted(userGroups []string, username, action st
return result
}
func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
func BaseAuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
resource := vars["name"]
reference, ok := vars["reference"]
/* NOTE:
since we only do READ actions in extensions, this middleware is enough for them because
it populates the context with user relevant data to be processed by each individual extension
*/
if request.Method == http.MethodOptions {
next.ServeHTTP(response, request)
@@ -286,17 +284,41 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
ctx := acCtrlr.getContext(acCtx, request)
/* Notes:
- since we only do READ actions in extensions, we can bypass authz for them
only need to know the username, whether the user is an admin, or what repos he can read.
let each extension to apply authorization on them using localCtx.AccessControlContext{}
*/
if isExtensionURI(request.RequestURI) {
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
})
}
}
func DistSpecAuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
if request.Method == http.MethodOptions {
next.ServeHTTP(response, request)
return
}
vars := mux.Vars(request)
resource := vars["name"]
reference, ok := vars["reference"]
acCtrlr := NewAccessController(ctlr.Config)
var identity string
var err error
// get acCtx built in authn and previous authz middlewares
acCtx, err := localCtx.GetAccessControlContext(request.Context())
if err != nil { // should never happen
authFail(response, ctlr.Config.HTTP.Realm, ctlr.Config.HTTP.Auth.FailDelay)
return
}
// get username from context made in authn.go
identity = acCtx.Username
var action string
if request.Method == http.MethodGet || request.Method == http.MethodHead {
action = Read
@@ -320,17 +342,12 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
action = Delete
}
can := acCtrlr.can(ctx, identity, action, resource) //nolint:contextcheck
can := acCtrlr.can(request.Context(), identity, action, resource) //nolint:contextcheck
if !can {
common.AuthzFail(response, ctlr.Config.HTTP.Realm, ctlr.Config.HTTP.Auth.FailDelay)
} else {
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
next.ServeHTTP(response, request) //nolint:contextcheck
}
})
}
}
func isExtensionURI(requestURI string) bool {
return strings.Contains(requestURI, constants.ExtPrefix) ||
requestURI == fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix)
}
+18 -15
View File
@@ -57,6 +57,8 @@ func NewRouteHandler(c *Controller) *RouteHandler {
func (rh *RouteHandler) SetupRoutes() {
prefixedRouter := rh.c.Router.PathPrefix(constants.RoutePrefix).Subrouter()
prefixedRouter.Use(AuthHandler(rh.c))
prefixedDistSpecRouter := prefixedRouter.NewRoute().Subrouter()
// authz is being enabled if AccessControl is specified
// if Authn is not present AccessControl will have only default policies
if rh.c.Config.HTTP.AccessControl != nil && !isBearerAuthEnabled(rh.c.Config) {
@@ -66,41 +68,42 @@ func (rh *RouteHandler) SetupRoutes() {
rh.c.Log.Info().Msg("default policy only access control is being enabled")
}
prefixedRouter.Use(AuthzHandler(rh.c))
prefixedRouter.Use(BaseAuthzHandler(rh.c))
prefixedDistSpecRouter.Use(DistSpecAuthzHandler(rh.c))
}
applyCORSHeaders := getCORSHeadersHandler(rh.c.Config.HTTP.AllowOrigin)
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#endpoints
{
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/tags/list", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/tags/list", zreg.NameRegexp.String()),
applyCORSHeaders(rh.ListTags)).Methods(zcommon.AllowedMethods("GET")...)
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
applyCORSHeaders(rh.CheckManifest)).Methods(zcommon.AllowedMethods("HEAD")...)
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
applyCORSHeaders(rh.GetManifest)).Methods(zcommon.AllowedMethods("GET")...)
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
rh.UpdateManifest).Methods("PUT")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
rh.DeleteManifest).Methods("DELETE")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
rh.CheckBlob).Methods("HEAD")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
rh.GetBlob).Methods("GET")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
rh.DeleteBlob).Methods("DELETE")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/", zreg.NameRegexp.String()),
rh.CreateBlobUpload).Methods("POST")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
rh.GetBlobUpload).Methods("GET")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
rh.PatchBlobUpload).Methods("PATCH")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
rh.UpdateBlobUpload).Methods("PUT")
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", zreg.NameRegexp.String()),
rh.DeleteBlobUpload).Methods("DELETE")
// support for OCI artifact references
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/referrers/{digest}", zreg.NameRegexp.String()),
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/referrers/{digest}", zreg.NameRegexp.String()),
applyCORSHeaders(rh.GetReferrers)).Methods(zcommon.AllowedMethods("GET")...)
prefixedRouter.HandleFunc(constants.ExtCatalogPrefix,
applyCORSHeaders(rh.ListRepositories)).Methods(zcommon.AllowedMethods("GET")...)