[Identity-based Authorization] Add an option to specify a global policy for all repositories

using regex.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
Petu Eusebiu 2021-09-10 18:23:26 +03:00 committed by Ramkumar Chinchani
parent 3177f87403
commit 4f825a5e2f
8 changed files with 332 additions and 101 deletions

View File

@ -186,41 +186,69 @@ identities. An additional per-repository default policy can be specified for
identities not in the whitelist. Furthermore, a global admin policy can also be
specified which can override per-repository policies.
Glob patterns can also be used as repository paths.
Authorization is granted based on the longest path matched.
For example repos2/repo repository will match both "**" and "repos2/repo" keys,
in such case repos2/repo policy will be used because it's longer.
Because we use longest path matching we need a way to specify a global policy to override all the other policies.
For example, we can specify a global policy with "**" (will match all repos), but any other policy will overwrite it,
because it will be longer. So that's why we have the option to specify an adminPolicy.
Basically '**' means repositories not matched by any other per-repository policy.
create/update/delete can not be used without 'read' action, make sure read is always included in policies!
```
"accessControl": {
"repos1/repo": {
"policies": [
"**": { # matches all repos (which are not matched by any other per-repository policy)
"policies": [ # user based policies
{
"users": ["alice", "bob"],
"actions": ["create", "read", "update", "delete"]
},
{
"users": ["mallory"],
"actions": ["create", "read"]
"users": ["charlie"],
"actions": ["read", "create", "update"]
}
],
"defaultPolicy": ["read", "create"] # default policy which is applied for all users => so all users can read/create repositories
},
"tmp/**": { # matches all repos under tmp/ recursively
"defaultPolicy": ["read", "create", "update"] # so all users have read/create/update on all repos under tmp/ eg: tmp/infra/repo
},
"infra/*": { # matches all repos directly under infra/ (not recursively)
"policies": [
{
"users": ["alice", "bob"],
"actions": ["create", "read", "update", "delete"]
},
{
"users": ["mallory"],
"actions": ["create", "read"]
}
],
"defaultPolicy": ["read"]
},
"repos2/repo": {
"repos2/repo": { # matches only repos2/repo repository
"policies": [
{
"users": ["bob"],
"actions": ["read", "create"]
},
{
"users": ["mallory"],
"actions": ["create", "read"]
}
{
"users": ["bob"],
"actions": ["read", "create"]
},
{
"users": ["mallory"],
"actions": ["create", "read"]
}
],
"defaultPolicy": ["read"]
},
"adminPolicy": {
"adminPolicy": { # global admin policy (overrides per-repo policy)
"users": ["admin"],
"actions": ["read", "create", "update", "delete"]
}
}
```
## Logging
Enable and configure logging with:

View File

@ -14,7 +14,19 @@
"failDelay": 1
},
"accessControl": {
"repos1/repo": {
"**": {
"policies": [
{
"users": ["charlie"],
"actions": ["read", "create", "update"]
}
],
"defaultPolicy": ["read", "create"]
},
"tmp/**": {
"defaultPolicy": ["read", "create", "update"]
},
"infra/**": {
"policies": [
{
"users": ["alice", "bob"],
@ -30,7 +42,7 @@
"repos2/repo": {
"policies": [
{
"users": ["bob"],
"users": ["charlie"],
"actions": ["read", "create"]
},
{

View File

@ -7,6 +7,7 @@ import (
"strings"
"time"
glob "github.com/bmatcuk/doublestar/v4"
"github.com/gorilla/mux"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/log"
@ -33,8 +34,8 @@ type AccessController struct {
// AccessControlContext context passed down to http.Handlers.
type AccessControlContext struct {
userAllowedRepos []string
isAdmin bool
globPatterns map[string]bool
isAdmin bool
}
func NewAccessController(config *config.Config) *AccessController {
@ -44,27 +45,49 @@ func NewAccessController(config *config.Config) *AccessController {
}
}
// getReadRepos get repositories from config file that the user has READ perms.
func (ac *AccessController) getReadRepos(username string) []string {
var repos []string
// getReadRepos get glob patterns from config file that the user has or doesn't have READ perms.
// used to filter /v2/_catalog repositories based on user rights.
func (ac *AccessController) getReadGlobPatterns(username string) map[string]bool {
globPatterns := make(map[string]bool)
for r, pg := range ac.Config.Repositories {
for _, p := range pg.Policies {
if (contains(p.Users, username) && contains(p.Actions, READ)) ||
contains(pg.DefaultPolicy, READ) {
repos = append(repos, r)
for pattern, policyGroup := range ac.Config.Repositories {
// check default policy
if contains(policyGroup.DefaultPolicy, READ) {
globPatterns[pattern] = true
}
// check user based policy
for _, p := range policyGroup.Policies {
if contains(p.Users, username) && contains(p.Actions, READ) {
globPatterns[pattern] = true
}
}
// if not allowed then mark it
if _, ok := globPatterns[pattern]; !ok {
globPatterns[pattern] = false
}
}
return repos
return globPatterns
}
// can verifies if a user can do action on repository.
func (ac *AccessController) can(username, action, repository string) bool {
can := false
// check repo based policy
pg, ok := ac.Config.Repositories[repository]
var longestMatchedPattern string
for pattern := range ac.Config.Repositories {
matched, err := glob.Match(pattern, repository)
if err == nil {
if matched && len(pattern) > len(longestMatchedPattern) {
longestMatchedPattern = pattern
}
}
}
// check matched repo based policy
pg, ok := ac.Config.Repositories[longestMatchedPattern]
if ok {
can = isPermitted(username, action, pg)
}
@ -86,8 +109,8 @@ func (ac *AccessController) isAdmin(username string) bool {
// getContext builds ac context(allowed to read repos and if user is admin) and returns it.
func (ac *AccessController) getContext(username string, request *http.Request) context.Context {
userAllowedRepos := ac.getReadRepos(username)
acCtx := AccessControlContext{userAllowedRepos: userAllowedRepos}
readGlobPatterns := ac.getReadGlobPatterns(username)
acCtx := AccessControlContext{globPatterns: readGlobPatterns}
if ac.isAdmin(username) {
acCtx.isAdmin = true
@ -132,14 +155,23 @@ func contains(slice []string, item string) bool {
return false
}
func containsRepo(slice []string, item string) bool {
for _, v := range slice {
if strings.HasPrefix(item, v) {
return true
// returns either a user has or not rights on 'repository'.
func matchesRepo(globPatterns map[string]bool, repository string) bool {
var longestMatchedPattern string
// because of the longest path matching rule, we need to check all patterns from config
for pattern := range globPatterns {
matched, err := glob.Match(pattern, repository)
if err == nil {
if matched && len(pattern) > len(longestMatchedPattern) {
longestMatchedPattern = pattern
}
}
}
return false
allowed := globPatterns[longestMatchedPattern]
return allowed
}
func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
@ -149,11 +181,19 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
resource := vars["name"]
reference, ok := vars["reference"]
// bypass authz for /v2/ route
if request.RequestURI == "/v2/" {
next.ServeHTTP(response, request)
return
}
acCtrlr := NewAccessController(ctlr.Config)
username := getUsername(request)
ctx := acCtrlr.getContext(username, request)
if request.RequestURI == "/v2/_catalog" || request.RequestURI == "/v2/" {
// will return only repos on which client is authorized to read
if request.RequestURI == "/v2/_catalog" {
next.ServeHTTP(response, request.WithContext(ctx))
return
@ -193,7 +233,7 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
}
func getUsername(r *http.Request) string {
// this should work because it worked in auth middleware
// this should work because it was already parsed in authn middleware
basicAuth := r.Header.Get("Authorization")
s := strings.SplitN(basicAuth, " ", 2) //nolint:gomnd
b, _ := base64.StdEncoding.DecodeString(s[1])

View File

@ -162,7 +162,7 @@ func (c *Config) Validate(log log.Logger) error {
}
// LoadAccessControlConfig populates config.AccessControl struct with values from config.
func (c *Config) LoadAccessControlConfig() error {
func (c *Config) LoadAccessControlConfig(viperInstance *viper.Viper) error {
if c.HTTP.RawAccessControl == nil {
return nil
}
@ -176,19 +176,19 @@ func (c *Config) LoadAccessControlConfig() error {
var policyGroup PolicyGroup
if policy == "adminpolicy" {
adminPolicy := viper.GetStringMapStringSlice("http.accessControl.adminPolicy")
adminPolicy := viperInstance.GetStringMapStringSlice("http::accessControl::adminPolicy")
c.AccessControl.AdminPolicy.Actions = adminPolicy["actions"]
c.AccessControl.AdminPolicy.Users = adminPolicy["users"]
continue
}
err := viper.UnmarshalKey(fmt.Sprintf("http.accessControl.%s.policies", policy), &policies)
err := viperInstance.UnmarshalKey(fmt.Sprintf("http::accessControl::%s::policies", policy), &policies)
if err != nil {
return err
}
defaultPolicy := viper.GetStringSlice(fmt.Sprintf("http.accessControl.%s.defaultPolicy", policy))
defaultPolicy := viperInstance.GetStringSlice(fmt.Sprintf("http::accessControl::%s::defaultPolicy", policy))
policyGroup.Policies = policies
policyGroup.DefaultPolicy = defaultPolicy
c.AccessControl.Repositories[policy] = policyGroup

View File

@ -56,6 +56,7 @@ const (
UnauthorizedNamespace = "fortknox/notallowed"
ALICE = "alice"
AuthorizationNamespace = "authz/image"
AuthorizationAllRepos = "**"
)
type (
@ -1752,7 +1753,7 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
}
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationNamespace: config.PolicyGroup{
AuthorizationAllRepos: config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{},
@ -1787,8 +1788,14 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
blob := []byte("hello, blob!")
digest := godigest.FromBytes(blob).String()
// unauthenticated clients should not have access to /v2/
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 401)
// everybody should have access to /v2/
resp, err := resty.R().SetBasicAuth(username, passphrase).
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
@ -1804,7 +1811,6 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
err = json.Unmarshal(resp.Body(), &e)
So(err, ShouldBeNil)
// first let's use only repositories based policies
// should get 403 without create
resp, err = resty.R().SetBasicAuth(username, passphrase).
Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
@ -1812,11 +1818,13 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// add test user to repo's policy with create perm
conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users =
append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users, "test")
conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "create")
// first let's use global based policies
// add test user to global policy with create perm
conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Users =
append(conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Users, "test")
conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions, "create")
// now it should get 202
resp, err = resty.R().SetBasicAuth(username, passphrase).
@ -1837,18 +1845,104 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
// head blob should get 403 with read perm
// head blob should get 403 without read perm
resp, err = resty.R().SetBasicAuth(username, passphrase).
Head(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// get blob should get 403 without read perm
// get tags without read access should get 403
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// get tags with read access should get 200
conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions, "read")
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// head blob should get 200 now
resp, err = resty.R().SetBasicAuth(username, passphrase).
Head(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get blob should get 200 now
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// delete blob should get 403 without delete perm
resp, err = resty.R().SetBasicAuth(username, passphrase).
Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// add delete perm on repo
conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationAllRepos].Policies[0].Actions, "delete")
// delete blob should get 202
resp, err = resty.R().SetBasicAuth(username, passphrase).
Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
// now let's use only repository based policies
// add test user to repo's policy with create perm
// longest path matching should match the repo and not **/*
conf.AccessControl.Repositories[AuthorizationNamespace] = config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{},
Actions: []string{},
},
},
DefaultPolicy: []string{},
}
conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users =
append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users, "test")
conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "create")
// now it should get 202
resp, err = resty.R().SetBasicAuth(username, passphrase).
Post(baseURL + "/v2/" + AuthorizationNamespace + "/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)
// head blob should get 403 without read perm
resp, err = resty.R().SetBasicAuth(username, passphrase).
Head(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// get tags without read access should get 403
@ -1861,6 +1955,7 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
// get tags with read access should get 200
conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "read")
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
@ -1899,6 +1994,12 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
// remove permissions on **/* so it will not interfere with zot-test namespace
repoPolicy := conf.AccessControl.Repositories[AuthorizationAllRepos]
repoPolicy.Policies = []config.Policy{}
repoPolicy.DefaultPolicy = []string{}
conf.AccessControl.Repositories[AuthorizationAllRepos] = repoPolicy
// get manifest should get 403, we don't have perm at all on this repo
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
@ -1965,7 +2066,7 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
// now use default repo policy
conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = []string{}
repoPolicy := conf.AccessControl.Repositories["zot-test"]
repoPolicy = conf.AccessControl.Repositories["zot-test"]
repoPolicy.DefaultPolicy = []string{"update"}
conf.AccessControl.Repositories["zot-test"] = repoPolicy
@ -2006,17 +2107,17 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
repoPolicy.DefaultPolicy = []string{}
conf.AccessControl.Repositories[AuthorizationNamespace] = repoPolicy
repoPolicy = conf.AccessControl.Repositories["zot-test"]
repoPolicy.Policies = []config.Policy{}
repoPolicy.DefaultPolicy = []string{}
conf.AccessControl.Repositories["zot-test"] = repoPolicy
resp, err = resty.R().SetBasicAuth(username, passphrase).
Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// let's use admin policy
// remove all repo based policy
delete(conf.AccessControl.Repositories, AuthorizationNamespace)
delete(conf.AccessControl.Repositories, "zot-test")
// whithout any perm should get 403
resp, err = resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")

View File

@ -1234,7 +1234,7 @@ func (rh *RouteHandler) ListRepositories(response http.ResponseWriter, request *
}
for _, r := range combineRepoList {
if containsRepo(acCtx.userAllowedRepos, r) || acCtx.isAdmin {
if acCtx.isAdmin || matchesRepo(acCtx.globPatterns, r) {
repos = append(repos, r)
}
}

View File

@ -186,25 +186,7 @@ func NewRootCmd() *cobra.Command {
return rootCmd
}
func LoadConfiguration(config *config.Config, configPath string) {
viper.SetConfigFile(configPath)
if err := viper.ReadInConfig(); err != nil {
log.Error().Err(err).Msg("error while reading configuration")
panic(err)
}
metaData := &mapstructure.Metadata{}
if err := viper.Unmarshal(&config, metadataConfig(metaData)); err != nil {
log.Error().Err(err).Msg("error while unmarshalling new config")
panic(err)
}
if len(metaData.Keys) == 0 || len(metaData.Unused) > 0 {
log.Error().Err(errors.ErrBadConfig).Msg("bad configuration, retry writing it")
panic(errors.ErrBadConfig)
}
func validateConfiguration(config *config.Config) {
// check authorization config, it should have basic auth enabled or ldap
if config.HTTP.RawAccessControl != nil {
if config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil) {
@ -228,14 +210,14 @@ func LoadConfiguration(config *config.Config, configPath string) {
}
}
// check glob patterns in sync are compilable
// check glob patterns in sync config are compilable
if config.Extensions != nil && config.Extensions.Sync != nil {
for _, regCfg := range config.Extensions.Sync.Registries {
if regCfg.Content != nil {
for _, content := range regCfg.Content {
ok := glob.ValidatePattern(content.Prefix)
if !ok {
log.Error().Err(glob.ErrBadPattern).Str("pattern", content.Prefix).Msg("pattern could not be compiled")
log.Error().Err(glob.ErrBadPattern).Str("pattern", content.Prefix).Msg("sync pattern could not be compiled")
panic(errors.ErrBadConfig)
}
}
@ -260,19 +242,57 @@ func LoadConfiguration(config *config.Config, configPath string) {
}
}
err := config.LoadAccessControlConfig()
if err != nil {
log.Error().Err(errors.ErrBadConfig).Msg("unable to unmarshal http.accessControl.key.policies")
panic(err)
}
// defaults
defualtTLSVerify := true
if config.Extensions != nil && config.Extensions.Sync != nil {
for id, regCfg := range config.Extensions.Sync.Registries {
if regCfg.TLSVerify == nil {
config.Extensions.Sync.Registries[id].TLSVerify = &defualtTLSVerify
// check glob patterns in authz config are compilable
if config.AccessControl != nil {
for pattern := range config.AccessControl.Repositories {
ok := glob.ValidatePattern(pattern)
if !ok {
log.Error().Err(glob.ErrBadPattern).Str("pattern", pattern).Msg("authorization pattern could not be compiled")
panic(errors.ErrBadConfig)
}
}
}
}
func LoadConfiguration(config *config.Config, configPath string) {
// Default is dot (.) but because we allow glob patterns in authz
// we need another key delimiter.
viperInstance := viper.NewWithOptions(viper.KeyDelimiter("::"))
viperInstance.SetConfigFile(configPath)
if err := viperInstance.ReadInConfig(); err != nil {
log.Error().Err(err).Msg("error while reading configuration")
panic(err)
}
metaData := &mapstructure.Metadata{}
if err := viperInstance.Unmarshal(&config, metadataConfig(metaData)); err != nil {
log.Error().Err(err).Msg("error while unmarshalling new config")
panic(err)
}
if len(metaData.Keys) == 0 || len(metaData.Unused) > 0 {
log.Error().Err(errors.ErrBadConfig).Msg("bad configuration, retry writing it")
panic(errors.ErrBadConfig)
}
err := config.LoadAccessControlConfig(viperInstance)
if err != nil {
log.Error().Err(err).Msg("unable to unmarshal config's accessControl")
panic(err)
}
// various config checks
validateConfiguration(config)
// defaults
defaultTLSVerify := true
if config.Extensions != nil && config.Extensions.Sync != nil {
for id, regCfg := range config.Extensions.Sync.Registries {
if regCfg.TLSVerify == nil {
config.Extensions.Sync.Registries[id].TLSVerify = &defaultTLSVerify
}
}
}

View File

@ -10,7 +10,6 @@ import (
"time"
. "github.com/smartystreets/goconvey/convey"
"github.com/spf13/viper"
"gopkg.in/resty.v1"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
@ -188,6 +187,40 @@ func TestVerify(t *testing.T) {
So(func() { _ = cli.NewRootCmd().Execute() }, ShouldPanic)
})
Convey("Test verify with bad authorization repo patterns", 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",
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1},
"accessControl":{"\|":{"policies":[],"defaultPolicy":[]}}}}`)
_, err = tmpfile.Write(content)
So(err, ShouldBeNil)
err = tmpfile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
So(func() { _ = cli.NewRootCmd().Execute() }, ShouldPanic)
})
Convey("Test verify sync config default tls value", 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",
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
"extensions":{"sync": {"registries": [{"url":"localhost:9999",
"content": [{"prefix":"repo**"}]}]}}}`)
_, err = tmpfile.Write(content)
So(err, ShouldBeNil)
err = tmpfile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
err = cli.NewRootCmd().Execute()
So(err, ShouldBeNil)
})
Convey("Test verify good config", t, func(c C) {
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
So(err, ShouldBeNil)
@ -209,9 +242,6 @@ func TestLoadConfig(t *testing.T) {
Convey("Test viper load config", t, func(c C) {
config := config.New()
So(func() { cli.LoadConfiguration(config, "../../examples/config-policy.json") }, ShouldNotPanic)
adminPolicy := viper.GetStringMapStringSlice("http.accessControl.adminPolicy")
So(config.AccessControl.AdminPolicy.Actions, ShouldResemble, adminPolicy["actions"])
So(config.AccessControl.AdminPolicy.Users, ShouldResemble, adminPolicy["users"])
})
}