mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
default policy only authorization
unit tests for manifest integrity when updating Signed-off-by: laurentiuNiculae <themelopeus@gmail.com>
This commit is contained in:
parent
e1a1bdff1a
commit
bb95af5b4d
7 changed files with 417 additions and 12 deletions
30
examples/config-default-authz.json
Normal file
30
examples/config-default-authz.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"distSpecVersion": "1.0.1",
|
||||||
|
"storage": {
|
||||||
|
"rootDirectory": "/tmp/zot"
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": "8080",
|
||||||
|
"realm": "zot",
|
||||||
|
"accessControl": {
|
||||||
|
"**": {
|
||||||
|
"defaultPolicy": ["read", "create"]
|
||||||
|
},
|
||||||
|
"tmp/**": {
|
||||||
|
"defaultPolicy": ["read", "create", "update"]
|
||||||
|
},
|
||||||
|
"infra/**": {
|
||||||
|
"defaultPolicy": ["read"]
|
||||||
|
},
|
||||||
|
"repos2/repo": {
|
||||||
|
"defaultPolicy": ["read"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"level": "debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -180,7 +180,14 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
acCtrlr := NewAccessController(ctlr.Config)
|
acCtrlr := NewAccessController(ctlr.Config)
|
||||||
username := getUsername(request)
|
|
||||||
|
// allow anonymous authz if no authn present and only default policies are present
|
||||||
|
username := ""
|
||||||
|
|
||||||
|
if isAuthnEnabled(ctlr.Config) {
|
||||||
|
username = getUsername(request)
|
||||||
|
}
|
||||||
|
|
||||||
ctx := acCtrlr.getContext(username, request)
|
ctx := acCtrlr.getContext(username, request)
|
||||||
|
|
||||||
// will return only repos on which client is authorized to read
|
// will return only repos on which client is authorized to read
|
||||||
|
@ -202,7 +209,7 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||||
if ok {
|
if ok {
|
||||||
is := ctlr.StoreController.GetImageStore(resource)
|
is := ctlr.StoreController.GetImageStore(resource)
|
||||||
tags, err := is.GetImageTags(resource)
|
tags, err := is.GetImageTags(resource)
|
||||||
// if repo exists and request's tag doesn't exist yet then action is UPDATE
|
// if repo exists and request's tag exists then action is UPDATE
|
||||||
if err == nil && common.Contains(tags, reference) && reference != "latest" {
|
if err == nil && common.Contains(tags, reference) && reference != "latest" {
|
||||||
action = UPDATE
|
action = UPDATE
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ func New() *Config {
|
||||||
Commit: Commit,
|
Commit: Commit,
|
||||||
BinaryType: BinaryType,
|
BinaryType: BinaryType,
|
||||||
Storage: GlobalStorageConfig{GC: true, GCDelay: storage.DefaultGCDelay, Dedupe: true},
|
Storage: GlobalStorageConfig{GC: true, GCDelay: storage.DefaultGCDelay, Dedupe: true},
|
||||||
HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080"},
|
HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080", Auth: &AuthConfig{FailDelay: 0}},
|
||||||
Log: &LogConfig{Level: "debug"},
|
Log: &LogConfig{Level: "debug"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2096,6 +2096,9 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
|
||||||
manifestBlob := resp.Body()
|
manifestBlob := resp.Body()
|
||||||
|
var manifest ispec.Manifest
|
||||||
|
err = json.Unmarshal(manifestBlob, &manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// put manifest should get 403 without create perm
|
// put manifest should get 403 without create perm
|
||||||
resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
|
resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
|
||||||
|
@ -2116,25 +2119,100 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
// create update config and post it.
|
||||||
|
cblob, cdigest := test.GetRandomImageConfig()
|
||||||
|
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Post(baseURL + "/v2/zot-test/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
loc = test.Location(baseURL, resp)
|
||||||
|
|
||||||
|
// uploading blob should get 201
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", cdigest.String()).
|
||||||
|
SetBody(cblob).
|
||||||
|
Put(loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
// create updated layer and post it
|
||||||
|
updateBlob := []byte("Hello, blob update!")
|
||||||
|
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Post(baseURL + "/v2/zot-test/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
loc = test.Location(baseURL, resp)
|
||||||
|
// uploading blob should get 201
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(updateBlob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", string(godigest.FromBytes(updateBlob))).
|
||||||
|
SetBody(updateBlob).
|
||||||
|
Put(loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
updatedManifest := ispec.Manifest{
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: cdigest,
|
||||||
|
Size: int64(len(cblob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(updateBlob),
|
||||||
|
Size: int64(len(updateBlob)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updatedManifest.SchemaVersion = 2
|
||||||
|
updatedManifestBlob, err := json.Marshal(updatedManifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// update manifest should get 403 without update perm
|
// update manifest should get 403 without update perm
|
||||||
resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
|
resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(updatedManifestBlob).
|
||||||
Put(baseURL + "/v2/zot-test/manifests/0.0.2")
|
Put(baseURL + "/v2/zot-test/manifests/0.0.2")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||||
|
|
||||||
|
// get the manifest and check if it's the old one
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Get(baseURL + "/v2/zot-test/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
So(resp.Body(), ShouldResemble, manifestBlob)
|
||||||
|
|
||||||
// add update perm on repo
|
// add update perm on repo
|
||||||
conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = append(conf.AccessControl.Repositories["zot-test"].Policies[0].Actions, "update") //nolint:lll // gofumpt conflicts with lll
|
conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = append(conf.AccessControl.Repositories["zot-test"].Policies[0].Actions, "update") //nolint:lll // gofumpt conflicts with lll
|
||||||
|
|
||||||
// update manifest should get 201 with update perm
|
// update manifest should get 201 with update perm
|
||||||
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
|
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
|
||||||
SetBody(manifestBlob).
|
SetBody(updatedManifestBlob).
|
||||||
Put(baseURL + "/v2/zot-test/manifests/0.0.2")
|
Put(baseURL + "/v2/zot-test/manifests/0.0.2")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
// get the manifest and check if it's the new updated one
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Get(baseURL + "/v2/zot-test/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
So(resp.Body(), ShouldResemble, updatedManifestBlob)
|
||||||
|
|
||||||
// now use default repo policy
|
// now use default repo policy
|
||||||
conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = []string{}
|
conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = []string{}
|
||||||
repoPolicy = conf.AccessControl.Repositories["zot-test"]
|
repoPolicy = conf.AccessControl.Repositories["zot-test"]
|
||||||
|
@ -2280,6 +2358,200 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
|
||||||
|
Convey("Make a new controller", t, func() {
|
||||||
|
const TestRepo = "my-repos/repo"
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
conf.HTTP.Auth = &config.AuthConfig{}
|
||||||
|
conf.AccessControl = &config.AccessControlConfig{
|
||||||
|
Repositories: config.Repositories{
|
||||||
|
TestRepo: config.PolicyGroup{
|
||||||
|
DefaultPolicy: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
dir := t.TempDir()
|
||||||
|
ctlr.Config.Storage.RootDirectory = dir
|
||||||
|
|
||||||
|
go startServer(ctlr)
|
||||||
|
defer stopServer(ctlr)
|
||||||
|
test.WaitTillServerReady(baseURL)
|
||||||
|
|
||||||
|
blob := []byte("hello, blob!")
|
||||||
|
digest := godigest.FromBytes(blob).String()
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
|
||||||
|
resp, err = resty.R().Get(baseURL + "/v2/_catalog")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
var e api.Error
|
||||||
|
err = json.Unmarshal(resp.Body(), &e)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// should get 403 without create
|
||||||
|
resp, err = resty.R().Post(baseURL + "/v2/" + TestRepo + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||||
|
|
||||||
|
if entry, ok := conf.AccessControl.Repositories[TestRepo]; ok {
|
||||||
|
entry.DefaultPolicy = []string{"create", "read"}
|
||||||
|
conf.AccessControl.Repositories[TestRepo] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// now it should get 202
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Post(baseURL + "/v2/" + TestRepo + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
loc := resp.Header().Get("Location")
|
||||||
|
|
||||||
|
// uploading blob should get 201
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", digest).
|
||||||
|
SetBody(blob).
|
||||||
|
Put(baseURL + loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
cblob, cdigest := test.GetRandomImageConfig()
|
||||||
|
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
Post(baseURL + "/v2/" + TestRepo + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
loc = test.Location(baseURL, resp)
|
||||||
|
|
||||||
|
// uploading blob should get 201
|
||||||
|
resp, err = resty.R().SetBasicAuth(username, passphrase).
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", cdigest.String()).
|
||||||
|
SetBody(cblob).
|
||||||
|
Put(loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
manifest := ispec.Manifest{
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: cdigest,
|
||||||
|
Size: int64(len(cblob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(blob),
|
||||||
|
Size: int64(len(blob)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifest.SchemaVersion = 2
|
||||||
|
manifestBlob, err := json.Marshal(manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
|
||||||
|
SetBody(manifestBlob).
|
||||||
|
Put(baseURL + "/v2/" + TestRepo + "/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
updateBlob := []byte("Hello, blob update!")
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
Post(baseURL + "/v2/" + TestRepo + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
loc = test.Location(baseURL, resp)
|
||||||
|
// uploading blob should get 201
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(updateBlob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", string(godigest.FromBytes(updateBlob))).
|
||||||
|
SetBody(updateBlob).
|
||||||
|
Put(loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
updatedManifest := ispec.Manifest{
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: cdigest,
|
||||||
|
Size: int64(len(cblob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(updateBlob),
|
||||||
|
Size: int64(len(updateBlob)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updatedManifest.SchemaVersion = 2
|
||||||
|
updatedManifestBlob, err := json.Marshal(updatedManifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// update manifest should get 403 without update perm
|
||||||
|
resp, err = resty.R().SetBody(updatedManifestBlob).
|
||||||
|
Put(baseURL + "/v2/" + TestRepo + "/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||||
|
|
||||||
|
// get the manifest and check if it's the old one
|
||||||
|
resp, err = resty.R().
|
||||||
|
Get(baseURL + "/v2/" + TestRepo + "/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
So(resp.Body(), ShouldResemble, manifestBlob)
|
||||||
|
|
||||||
|
// add update perm on repo
|
||||||
|
if entry, ok := conf.AccessControl.Repositories[TestRepo]; ok {
|
||||||
|
entry.DefaultPolicy = []string{"create", "read", "update"}
|
||||||
|
conf.AccessControl.Repositories[TestRepo] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// update manifest should get 201 with update perm
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
|
||||||
|
SetBody(updatedManifestBlob).
|
||||||
|
Put(baseURL + "/v2/" + TestRepo + "/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||||
|
|
||||||
|
// get the manifest and check if it's the new updated one
|
||||||
|
resp, err = resty.R().
|
||||||
|
Get(baseURL + "/v2/" + TestRepo + "/manifests/0.0.2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
So(resp.Body(), ShouldResemble, updatedManifestBlob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidCases(t *testing.T) {
|
func TestInvalidCases(t *testing.T) {
|
||||||
Convey("Invalid repo dir", t, func() {
|
Convey("Invalid repo dir", t, func() {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
|
|
|
@ -57,9 +57,15 @@ func allowedMethods(method string) []string {
|
||||||
// nolint: contextcheck
|
// nolint: contextcheck
|
||||||
func (rh *RouteHandler) SetupRoutes() {
|
func (rh *RouteHandler) SetupRoutes() {
|
||||||
rh.c.Router.Use(AuthHandler(rh.c))
|
rh.c.Router.Use(AuthHandler(rh.c))
|
||||||
// authz is being enabled because authn is found
|
// authz is being enabled if AccessControl is specified
|
||||||
if rh.c.Config.AccessControl != nil && !isBearerAuthEnabled(rh.c.Config) && isAuthnEnabled(rh.c.Config) {
|
// if Authn is not present AccessControl will have only default policies
|
||||||
|
if rh.c.Config.AccessControl != nil && !isBearerAuthEnabled(rh.c.Config) {
|
||||||
|
if isAuthnEnabled(rh.c.Config) {
|
||||||
rh.c.Log.Info().Msg("access control is being enabled")
|
rh.c.Log.Info().Msg("access control is being enabled")
|
||||||
|
} else {
|
||||||
|
rh.c.Log.Info().Msg("default policy only access control is being enabled")
|
||||||
|
}
|
||||||
|
|
||||||
rh.c.Router.Use(AuthzHandler(rh.c))
|
rh.c.Router.Use(AuthzHandler(rh.c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -231,10 +231,8 @@ func validateConfiguration(config *config.Config) error {
|
||||||
// check authorization config, it should have basic auth enabled or ldap
|
// check authorization config, it should have basic auth enabled or ldap
|
||||||
if config.HTTP.RawAccessControl != nil {
|
if config.HTTP.RawAccessControl != nil {
|
||||||
if config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil) {
|
if config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil) {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
// checking for default policy only authorization config: no users, no policies but default policy
|
||||||
Msg("access control config requires httpasswd or ldap authentication to be enabled")
|
checkForDefaultPolicyConfig(config)
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,6 +309,15 @@ func validateConfiguration(config *config.Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkForDefaultPolicyConfig(config *config.Config) {
|
||||||
|
if !isDefaultPolicyConfig(config) {
|
||||||
|
log.Error().Err(errors.ErrBadConfig).
|
||||||
|
Msg("access control config requires httpasswd, ldap authentication " +
|
||||||
|
"or using only 'defaultPolicy' policies")
|
||||||
|
panic(errors.ErrBadConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func applyDefaultValues(config *config.Config, viperInstance *viper.Viper) {
|
func applyDefaultValues(config *config.Config, viperInstance *viper.Viper) {
|
||||||
defaultVal := true
|
defaultVal := true
|
||||||
|
|
||||||
|
@ -431,3 +438,30 @@ func LoadConfiguration(config *config.Config, configPath string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDefaultPolicyConfig(cfg *config.Config) bool {
|
||||||
|
adminPolicy := cfg.AccessControl.AdminPolicy
|
||||||
|
|
||||||
|
log.Info().Msg("checking if default authorization is possible")
|
||||||
|
|
||||||
|
if len(adminPolicy.Actions)+len(adminPolicy.Users) > 0 {
|
||||||
|
log.Info().Msg("admin policy detected, default authorization disabled")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repository := range cfg.AccessControl.Repositories {
|
||||||
|
for _, policy := range repository.Policies {
|
||||||
|
if len(policy.Actions)+len(policy.Users) > 0 {
|
||||||
|
log.Info().Interface("repository", repository).
|
||||||
|
Msg("repository with non-empty policy detected, default authorization disabled")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("default authorization detected")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -174,6 +174,62 @@ func TestVerify(t *testing.T) {
|
||||||
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldNotPanic)
|
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldNotPanic)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Test verify default authorization", t, func(c C) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
defer os.Remove(tmpfile.Name()) // clean up
|
||||||
|
content := []byte(`{"storage":{"rootDirectory":"/tmp/zot"},
|
||||||
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
||||||
|
"accessControl":{"**":{"defaultPolicy": ["read", "create"]},
|
||||||
|
"/repo":{"defaultPolicy": ["read", "create"]}
|
||||||
|
}}}`)
|
||||||
|
_, err = tmpfile.Write(content)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = tmpfile.Close()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
||||||
|
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldNotPanic)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Test verify default authorization fail", t, func(c C) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
defer os.Remove(tmpfile.Name()) // clean up
|
||||||
|
content := []byte(`{"storage":{"rootDirectory":"/tmp/zot"},
|
||||||
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
||||||
|
"accessControl":{"**":{"defaultPolicy": ["read", "create"]},
|
||||||
|
"/repo":{"defaultPolicy": ["read", "create"]},
|
||||||
|
"adminPolicy":{"users":["admin"],
|
||||||
|
"actions":["read","create","update","delete"]}
|
||||||
|
}}}`)
|
||||||
|
_, err = tmpfile.Write(content)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = tmpfile.Close()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
||||||
|
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldPanic)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Test verify default authorization fail", t, func(c C) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
defer os.Remove(tmpfile.Name()) // clean up
|
||||||
|
content := []byte(`{"storage":{"rootDirectory":"/tmp/zot"},
|
||||||
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
||||||
|
"accessControl":{"**":{"defaultPolicy": ["read", "create"]},
|
||||||
|
"/repo":{"defaultPolicy": ["read", "create"]},
|
||||||
|
"/repo2":{"policies": [{
|
||||||
|
"users": ["charlie"],
|
||||||
|
"actions": ["read", "create", "update"]}]}
|
||||||
|
}}}`)
|
||||||
|
_, err = tmpfile.Write(content)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = tmpfile.Close()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
||||||
|
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldPanic)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Test verify w/ sync and w/o filesystem storage", t, func(c C) {
|
Convey("Test verify w/ sync and w/o filesystem storage", t, func(c C) {
|
||||||
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
|
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
Loading…
Reference in a new issue