Remove AllowReadOnly and ReadOnly

Signed-off-by: Nicol Draghici <idraghic@cisco.com>

Remove check and set header every time

Signed-off-by: Nicol Draghici <idraghic@cisco.com>
This commit is contained in:
Nicol Draghici
2022-07-14 18:13:46 +03:00
committed by Ramkumar Chinchani
parent a5ed99178e
commit a702a2377e
33 changed files with 509 additions and 170 deletions
+36 -46
View File
@@ -86,22 +86,6 @@ func noPasswdAuth(realm string, config *config.Config) mux.MiddlewareFunc {
return
}
if config.HTTP.AllowReadAccess &&
config.HTTP.TLS.CACert != "" &&
request.TLS.VerifiedChains == nil &&
request.Method != http.MethodGet && request.Method != http.MethodHead {
authFail(response, realm, 5) //nolint:gomnd
return
}
if (request.Method != http.MethodGet && request.Method != http.MethodHead) && config.HTTP.ReadOnly {
// Reject modification requests in read-only mode
response.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Process request
next.ServeHTTP(response, request)
})
@@ -197,52 +181,30 @@ func basicAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
return
}
if (request.Method == http.MethodGet || request.Method == http.MethodHead) && ctlr.Config.HTTP.AllowReadAccess {
if request.Header.Get("Authorization") == "" && anonymousPolicyExists(ctlr.Config.AccessControl) {
// Process request
next.ServeHTTP(response, request)
return
}
if (request.Method != http.MethodGet && request.Method != http.MethodHead) && ctlr.Config.HTTP.ReadOnly {
response.WriteHeader(http.StatusMethodNotAllowed)
return
}
basicAuth := request.Header.Get("Authorization")
if basicAuth == "" {
authFail(response, realm, delay)
return
}
splitStr := strings.SplitN(basicAuth, " ", 2) //nolint:gomnd
if len(splitStr) != 2 || strings.ToLower(splitStr[0]) != "basic" {
authFail(response, realm, delay)
return
}
decodedStr, err := base64.StdEncoding.DecodeString(splitStr[1])
username, passphrase, err := getUsernamePasswordBasicAuth(request)
if err != nil {
ctlr.Log.Error().Err(err).Msg("failed to parse authorization header")
authFail(response, realm, delay)
return
}
pair := strings.SplitN(string(decodedStr), ":", 2) //nolint:gomnd
// nolint:gomnd
if len(pair) != 2 {
authFail(response, realm, delay)
// some client tools might send Authorization: Basic Og== (decoded into ":")
// empty username and password
if username == "" && passphrase == "" && anonymousPolicyExists(ctlr.Config.AccessControl) {
// Process request
next.ServeHTTP(response, request)
return
}
username := pair[0]
passphrase := pair[1]
// first, HTTPPassword authN (which is local)
passphraseHash, ok := credMap[username]
if ok {
@@ -297,3 +259,31 @@ func authFail(w http.ResponseWriter, realm string, delay int) {
w.Header().Set("Content-Type", "application/json")
WriteJSON(w, http.StatusUnauthorized, NewErrorList(NewError(UNAUTHORIZED)))
}
func getUsernamePasswordBasicAuth(request *http.Request) (string, string, error) {
basicAuth := request.Header.Get("Authorization")
if basicAuth == "" {
return "", "", errors.ErrParsingAuthHeader
}
splitStr := strings.SplitN(basicAuth, " ", 2) //nolint:gomnd
if len(splitStr) != 2 || strings.ToLower(splitStr[0]) != "basic" {
return "", "", errors.ErrParsingAuthHeader
}
decodedStr, err := base64.StdEncoding.DecodeString(splitStr[1])
if err != nil {
return "", "", err
}
pair := strings.SplitN(string(decodedStr), ":", 2) //nolint:gomnd
if len(pair) != 2 { //nolint:gomnd
return "", "", errors.ErrParsingAuthHeader
}
username := pair[0]
passphrase := pair[1]
return username, passphrase, nil
}
+34 -16
View File
@@ -2,10 +2,8 @@ package api
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"
glob "github.com/bmatcuk/doublestar/v4"
@@ -140,7 +138,14 @@ func isPermitted(username, action string, policyGroup config.PolicyGroup) bool {
// check defaultPolicy
if !result {
if common.Contains(policyGroup.DefaultPolicy, action) {
if common.Contains(policyGroup.DefaultPolicy, action) && username != "" {
result = true
}
}
// check anonymousPolicy
if !result {
if common.Contains(policyGroup.AnonymousPolicy, action) && username == "" {
result = true
}
}
@@ -184,10 +189,19 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
acCtrlr := NewAccessController(ctlr.Config)
// allow anonymous authz if no authn present and only default policies are present
username := ""
var username string
var err error
if isAuthnEnabled(ctlr.Config) {
username = getUsername(request)
/* To be implemented: verify client certs and get its username(subject DN)
if request.TLS.VerifiedChains != nil, then get subject DN
issue: https: //github.com/project-zot/zot/issues/614 */
if isAuthnEnabled(ctlr.Config) && request.Header.Get("Authorization") != "" {
username, _, err = getUsernamePasswordBasicAuth(request)
if err != nil {
authFail(response, ctlr.Config.HTTP.Realm, ctlr.Config.HTTP.Auth.FailDelay)
}
}
ctx := acCtrlr.getContext(username, request)
@@ -232,19 +246,23 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc {
}
}
func getUsername(r *http.Request) string {
// 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])
pair := strings.SplitN(string(b), ":", 2) //nolint:gomnd
return pair[0]
}
func authzFail(w http.ResponseWriter, realm string, delay int) {
time.Sleep(time.Duration(delay) * time.Second)
w.Header().Set("WWW-Authenticate", realm)
w.Header().Set("Content-Type", "application/json")
WriteJSON(w, http.StatusForbidden, NewErrorList(NewError(DENIED)))
}
func anonymousPolicyExists(config *config.AccessControlConfig) bool {
if config == nil {
return false
}
for _, repository := range config.Repositories {
if len(repository.AnonymousPolicy) > 0 {
return true
}
}
return false
}
+7 -5
View File
@@ -68,8 +68,6 @@ type HTTPConfig struct {
Auth *AuthConfig
RawAccessControl map[string]interface{} `mapstructure:"accessControl,omitempty"`
Realm string
AllowReadAccess bool `mapstructure:",omitempty"`
ReadOnly bool `mapstructure:",omitempty"`
Ratelimit *RatelimitConfig `mapstructure:",omitempty"`
}
@@ -112,8 +110,9 @@ type AccessControlConfig struct {
type Repositories map[string]PolicyGroup
type PolicyGroup struct {
Policies []Policy
DefaultPolicy []string
Policies []Policy
DefaultPolicy []string
AnonymousPolicy []string
}
type Policy struct {
@@ -193,8 +192,11 @@ func (c *Config) LoadAccessControlConfig(viperInstance *viper.Viper) error {
}
defaultPolicy := viperInstance.GetStringSlice(fmt.Sprintf("http::accessControl::%s::defaultPolicy", policy))
policyGroup.Policies = policies
policyGroup.DefaultPolicy = defaultPolicy
anonymousPolicy := viperInstance.GetStringSlice(fmt.Sprintf("http::accessControl::%s::anonymousPolicy", policy))
policyGroup.Policies = policies
policyGroup.AnonymousPolicy = anonymousPolicy
c.AccessControl.Repositories[policy] = policyGroup
}
+2 -1
View File
@@ -190,7 +190,8 @@ func (c *Controller) Run(reloadCtx context.Context) error {
if c.Config.HTTP.TLS.CACert != "" {
clientAuth := tls.VerifyClientCertIfGiven
if (c.Config.HTTP.Auth == nil || c.Config.HTTP.Auth.HTPasswd.Path == "") && !c.Config.HTTP.AllowReadAccess {
if (c.Config.HTTP.Auth == nil || c.Config.HTTP.Auth.HTPasswd.Path == "") &&
!anonymousPolicyExists(c.Config.AccessControl) {
clientAuth = tls.RequireAndVerifyClientCert
}
+258 -21
View File
@@ -928,7 +928,14 @@ func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
Cert: ServerCert,
Key: ServerKey,
}
conf.HTTP.AllowReadAccess = true
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
AnonymousPolicy: []string{"read"},
},
},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
@@ -960,7 +967,7 @@ func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
// without creds, writes should fail
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
})
}
@@ -1051,7 +1058,14 @@ func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
Key: ServerKey,
CACert: CACert,
}
conf.HTTP.AllowReadAccess = true
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
AnonymousPolicy: []string{"read"},
},
},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
@@ -1079,7 +1093,7 @@ func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
// without creds, writes should fail
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// setup TLS mutual auth
cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
@@ -1210,7 +1224,14 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
Key: ServerKey,
CACert: CACert,
}
conf.HTTP.AllowReadAccess = true
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
AnonymousPolicy: []string{"read"},
},
},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
@@ -1252,7 +1273,7 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
// with only client certs, writes should fail
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// with client certs and creds, should get expected status code
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
@@ -1620,10 +1641,17 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
Service: aurl.Host,
},
}
conf.HTTP.AllowReadAccess = true
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
AnonymousPolicy: []string{"read"},
},
},
}
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
@@ -2360,7 +2388,60 @@ func TestAuthorizationWithBasicAuth(t *testing.T) {
})
}
func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
func TestGetUsername(t *testing.T) {
Convey("Make a new controller", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
htpasswdPath := test.MakeHtpasswdFileFromString(getCredString(username, passphrase))
defer os.Remove(htpasswdPath)
conf := config.New()
conf.HTTP.Port = port
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
ctlr := api.NewController(conf)
dir := t.TempDir()
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// test base64 encode
resp, err = resty.R().SetHeader("Authorization", "Basic should fail").Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// test "username:password" encoding
resp, err = resty.R().SetHeader("Authorization", "Basic dGVzdA==").Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// failed parsing authorization header
resp, err = resty.R().SetHeader("Authorization", "Basic ").Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
var e api.Error
err = json.Unmarshal(resp.Body(), &e)
So(err, ShouldBeNil)
})
}
func TestAuthorizationWithOnlyAnonymousPolicy(t *testing.T) {
Convey("Make a new controller", t, func() {
const TestRepo = "my-repos/repo"
port := test.GetFreePort()
@@ -2372,7 +2453,7 @@ func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
TestRepo: config.PolicyGroup{
DefaultPolicy: []string{},
AnonymousPolicy: []string{},
},
},
}
@@ -2408,21 +2489,19 @@ func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
if entry, ok := conf.AccessControl.Repositories[TestRepo]; ok {
entry.DefaultPolicy = []string{"create", "read"}
entry.AnonymousPolicy = []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/")
resp, err = resty.R().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))).
resp, err = resty.R().SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
SetHeader("Content-Type", "application/octet-stream").
SetQueryParam("digest", digest).
SetBody(blob).
@@ -2433,16 +2512,14 @@ func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
cblob, cdigest := test.GetRandomImageConfig()
resp, err = resty.R().SetBasicAuth(username, passphrase).
Post(baseURL + "/v2/" + TestRepo + "/blobs/uploads/")
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().SetBasicAuth(username, passphrase).
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
resp, err = resty.R().SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
SetHeader("Content-Type", "application/octet-stream").
SetQueryParam("digest", cdigest.String()).
SetBody(cblob).
@@ -2531,7 +2608,7 @@ func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
// add update perm on repo
if entry, ok := conf.AccessControl.Repositories[TestRepo]; ok {
entry.DefaultPolicy = []string{"create", "read", "update"}
entry.AnonymousPolicy = []string{"create", "read", "update"}
conf.AccessControl.Repositories[TestRepo] = entry
}
@@ -2554,6 +2631,160 @@ func TestAuthorizationWithOnlyDefaultPolicy(t *testing.T) {
})
}
func TestAuthorizationWithMultiplePolicies(t *testing.T) {
Convey("Make a new controller", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
// have two users: "test" user for user Policy, and "bob" for default policy
htpasswdPath := test.MakeHtpasswdFileFromString(getCredString(username, passphrase) +
"\n" + getCredString("bob", passphrase))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
// config with all policy types, to test that the correct one is applied in each case
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{},
Actions: []string{},
},
},
DefaultPolicy: []string{},
AnonymousPolicy: []string{},
},
},
AdminPolicy: config.Policy{
Users: []string{},
Actions: []string{},
},
}
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
blob := []byte("hello, blob!")
digest := godigest.FromBytes(blob).String()
// unauthenticated clients should not have access to /v2/, no policy is applied since none exists
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 401)
repoPolicy := conf.AccessControl.Repositories[AuthorizationAllRepos]
repoPolicy.AnonymousPolicy = append(repoPolicy.AnonymousPolicy, "read")
conf.AccessControl.Repositories[AuthorizationAllRepos] = repoPolicy
// should have access to /v2/, anonymous policy is applied, "read" allowed
resp, err = resty.R().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")
// now it should get 202, user has the permission set on "create"
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
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)
repoPolicy.DefaultPolicy = append(repoPolicy.DefaultPolicy, "read")
conf.AccessControl.Repositories[AuthorizationAllRepos] = repoPolicy
// with read permission should get 200, because default policy allows reading now
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)
// get tags with default read access should be ok, since the user is now "bob" and default policy is applied
resp, err = resty.R().SetBasicAuth("bob", passphrase).
Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get tags with default policy read access
resp, err = resty.R().Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get tags with anonymous read access should be ok
resp, err = resty.R().Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// without create permission should get 403, since "bob" can only read(default policy applied)
resp, err = resty.R().SetBasicAuth("bob", passphrase).
Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// add read permission to user "bob"
conf.AccessControl.AdminPolicy.Users = append(conf.AccessControl.AdminPolicy.Users, "bob")
conf.AccessControl.AdminPolicy.Actions = append(conf.AccessControl.AdminPolicy.Actions, "create")
// added create permission to user "bob", should be allowed now
resp, err = resty.R().SetBasicAuth("bob", passphrase).
Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
})
}
func TestInvalidCases(t *testing.T) {
Convey("Invalid repo dir", t, func() {
port := test.GetFreePort()
@@ -2627,7 +2858,13 @@ func TestHTTPReadOnly(t *testing.T) {
conf := config.New()
conf.HTTP.Port = port
// enable read-only mode
conf.HTTP.ReadOnly = true
conf.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
AuthorizationAllRepos: config.PolicyGroup{
DefaultPolicy: []string{"read"},
},
},
}
htpasswdPath := test.MakeHtpasswdFileFromString(testString)
defer os.Remove(htpasswdPath)
@@ -2653,7 +2890,7 @@ func TestHTTPReadOnly(t *testing.T) {
Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusMethodNotAllowed)
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
// with invalid creds, it should fail
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
+5 -7
View File
@@ -140,13 +140,11 @@ func (rh *RouteHandler) CheckVersionSupport(response http.ResponseWriter, reques
response.Header().Set(constants.DistAPIVersion, "registry/2.0")
// NOTE: compatibility workaround - return this header in "allowed-read" mode to allow for clients to
// work correctly
if rh.c.Config.HTTP.AllowReadAccess {
if rh.c.Config.HTTP.Auth != nil {
if rh.c.Config.HTTP.Auth.Bearer != nil {
response.Header().Set("WWW-Authenticate", fmt.Sprintf("bearer realm=%s", rh.c.Config.HTTP.Auth.Bearer.Realm))
} else {
response.Header().Set("WWW-Authenticate", fmt.Sprintf("basic realm=%s", rh.c.Config.HTTP.Realm))
}
if rh.c.Config.HTTP.Auth != nil {
if rh.c.Config.HTTP.Auth.Bearer != nil {
response.Header().Set("WWW-Authenticate", fmt.Sprintf("bearer realm=%s", rh.c.Config.HTTP.Auth.Bearer.Realm))
} else {
response.Header().Set("WWW-Authenticate", fmt.Sprintf("basic realm=%s", rh.c.Config.HTTP.Realm))
}
}