feat(mgmt): added mgmt extension which returns current zot configuration (#1198)

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
peusebiu
2023-03-09 20:43:26 +02:00
committed by GitHub
parent 4c156234cb
commit f04e66a5e2
21 changed files with 807 additions and 33 deletions
+18 -10
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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"`
}
+2
View File
@@ -7,4 +7,6 @@ const (
// zot specific extensions.
ExtSearchPrefix = "/_zot/ext/search"
FullSearchPrefix = RoutePrefix + ExtSearchPrefix
ExtMgmtPrefix = "/_zot/ext/mgmt"
FullMgmtPrefix = RoutePrefix + ExtMgmtPrefix
)
+60
View File
@@ -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()
+1
View File
@@ -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)
}
}