mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 12:28:01 +08:00
feat(mgmt): added mgmt extension which returns current zot configuration (#1198)
Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
+18
-10
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
)
|
||||
|
||||
@@ -186,12 +187,17 @@ func basicAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if request.Header.Get("Authorization") == "" && anonymousPolicyExists(ctlr.Config.HTTP.AccessControl) {
|
||||
// Process request
|
||||
ctx := getReqContextWithAuthorization("", []string{}, request)
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
// we want to bypass auth for mgmt route
|
||||
isMgmtRequested := request.RequestURI == constants.FullMgmtPrefix
|
||||
|
||||
return
|
||||
if request.Header.Get("Authorization") == "" {
|
||||
if anonymousPolicyExists(ctlr.Config.HTTP.AccessControl) || isMgmtRequested {
|
||||
// Process request
|
||||
ctx := getReqContextWithAuthorization("", []string{}, request)
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
username, passphrase, err := getUsernamePasswordBasicAuth(request)
|
||||
@@ -204,12 +210,14 @@ func basicAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
|
||||
// some client tools might send Authorization: Basic Og== (decoded into ":")
|
||||
// empty username and password
|
||||
if username == "" && passphrase == "" && anonymousPolicyExists(ctlr.Config.HTTP.AccessControl) {
|
||||
// Process request
|
||||
ctx := getReqContextWithAuthorization("", []string{}, request)
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
if username == "" && passphrase == "" {
|
||||
if anonymousPolicyExists(ctlr.Config.HTTP.AccessControl) || isMgmtRequested {
|
||||
// Process request
|
||||
ctx := getReqContextWithAuthorization("", []string{}, request)
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// first, HTTPPassword authN (which is local)
|
||||
|
||||
+10
-9
@@ -240,9 +240,13 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
return
|
||||
}
|
||||
|
||||
/* we want to bypass auth/authz for mgmt in case of authFail() authzFail()
|
||||
unauthenticated users should have access to this route, but we also need to know if the user is an admin
|
||||
*/
|
||||
isMgmtRequested := request.RequestURI == constants.FullMgmtPrefix
|
||||
|
||||
acCtrlr := NewAccessController(ctlr.Config)
|
||||
|
||||
// allow anonymous authz if no authn present and only default policies are present
|
||||
var identity string
|
||||
var err error
|
||||
|
||||
@@ -264,6 +268,7 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
for _, cert := range request.TLS.PeerCertificates {
|
||||
identity = cert.Subject.CommonName
|
||||
}
|
||||
|
||||
// if we still don't have an identity
|
||||
if identity == "" {
|
||||
acCtrlr.Log.Info().Msg("couldn't get identity from TLS certificate")
|
||||
@@ -274,14 +279,10 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
|
||||
ctx := acCtrlr.getContext(identity, request)
|
||||
|
||||
// will return only repos on which client is authorized to read
|
||||
if request.RequestURI == fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix) {
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(request.RequestURI, constants.FullSearchPrefix) {
|
||||
// for extensions we only need to know if the user is admin and what repos he can read, so run next()
|
||||
if request.RequestURI == fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix) ||
|
||||
strings.Contains(request.RequestURI, constants.FullSearchPrefix) ||
|
||||
isMgmtRequested {
|
||||
next.ServeHTTP(response, request.WithContext(ctx)) //nolint:contextcheck
|
||||
|
||||
return
|
||||
|
||||
@@ -69,7 +69,7 @@ type HTTPConfig struct {
|
||||
AllowOrigin string // comma separated
|
||||
TLS *TLSConfig
|
||||
Auth *AuthConfig
|
||||
AccessControl *AccessControlConfig
|
||||
AccessControl *AccessControlConfig `mapstructure:"accessControl,omitempty"`
|
||||
Realm string
|
||||
Ratelimit *RatelimitConfig `mapstructure:",omitempty"`
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ const (
|
||||
// zot specific extensions.
|
||||
ExtSearchPrefix = "/_zot/ext/search"
|
||||
FullSearchPrefix = RoutePrefix + ExtSearchPrefix
|
||||
ExtMgmtPrefix = "/_zot/ext/mgmt"
|
||||
FullMgmtPrefix = RoutePrefix + ExtMgmtPrefix
|
||||
)
|
||||
|
||||
@@ -3138,6 +3138,12 @@ func TestAuthorizationWithMultiplePolicies(t *testing.T) {
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// with empty username:password
|
||||
resp, err = resty.R().SetHeader("Authorization", "Basic Og==").Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// add "test" user to global policy with create permission
|
||||
repoPolicy.Policies[0].Users = append(repoPolicy.Policies[0].Users, "test")
|
||||
repoPolicy.Policies[0].Actions = append(repoPolicy.Policies[0].Actions, "create")
|
||||
@@ -7290,6 +7296,60 @@ func TestDistSpecExtensions(t *testing.T) {
|
||||
So(extensionList.Extensions[0].Endpoints[0], ShouldEqual, constants.FullSearchPrefix)
|
||||
})
|
||||
|
||||
Convey("start zot server with search and mgmt extensions", t, func(c C) {
|
||||
conf := config.New()
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf.HTTP.Port = port
|
||||
|
||||
defaultVal := true
|
||||
|
||||
searchConfig := &extconf.SearchConfig{
|
||||
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||
}
|
||||
|
||||
mgmtConfg := &extconf.MgmtConfig{
|
||||
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||
}
|
||||
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: searchConfig,
|
||||
Mgmt: mgmtConfg,
|
||||
}
|
||||
|
||||
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
||||
So(err, ShouldBeNil)
|
||||
conf.Log.Output = logFile.Name()
|
||||
defer os.Remove(logFile.Name()) // clean up
|
||||
|
||||
ctlr := makeController(conf, t.TempDir(), "")
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
var extensionList distext.ExtensionList
|
||||
|
||||
resp, err := resty.R().Get(baseURL + constants.RoutePrefix + constants.ExtOciDiscoverPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
err = json.Unmarshal(resp.Body(), &extensionList)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(extensionList.Extensions), ShouldEqual, 2)
|
||||
So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 1)
|
||||
So(len(extensionList.Extensions[1].Endpoints), ShouldEqual, 1)
|
||||
So(extensionList.Extensions[0].Name, ShouldEqual, "_zot")
|
||||
So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md")
|
||||
So(extensionList.Extensions[0].Description, ShouldNotBeEmpty)
|
||||
So(extensionList.Extensions[0].Endpoints[0], ShouldEqual, constants.FullSearchPrefix)
|
||||
So(extensionList.Extensions[1].Name, ShouldEqual, "_zot")
|
||||
So(extensionList.Extensions[1].URL, ShouldContainSubstring, "_zot.md")
|
||||
So(extensionList.Extensions[1].Description, ShouldNotBeEmpty)
|
||||
So(extensionList.Extensions[1].Endpoints[0], ShouldEqual, constants.FullMgmtPrefix)
|
||||
})
|
||||
|
||||
Convey("start minimal zot server", t, func(c C) {
|
||||
conf := config.New()
|
||||
port := test.GetFreePort()
|
||||
|
||||
@@ -128,6 +128,7 @@ func (rh *RouteHandler) SetupRoutes() {
|
||||
ext.SetupMetricsRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, AuthHandler(rh.c), rh.c.Log)
|
||||
ext.SetupSearchRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.RepoDB, rh.c.CveInfo, rh.c.Log)
|
||||
ext.SetupUIRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.Log)
|
||||
ext.SetupMgmtRoutes(rh.c.Config, prefixedRouter, rh.c.Log)
|
||||
gqlPlayground.SetupGQLPlaygroundRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.Log)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user