mirror of
https://github.com/project-zot/zot.git
synced 2025-01-06 22:40:28 -05:00
compat: when in "world-readable" mode, return the WWW-Authenticate
header containers/image is the dominant client library to interact with registries. It detects which authentication to use based on the WWW-Authenticate header returned when pinging "/v2/" end-point. If we didn't return this header, then creds are not used for other write-protected end-points. Hence, the compatibility fix.
This commit is contained in:
parent
b2338b4819
commit
026b009dbb
2 changed files with 192 additions and 0 deletions
|
@ -986,6 +986,186 @@ func TestBearerAuth(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
|
Convey("Make a new controller", t, func() {
|
||||||
|
authTestServer := makeAuthTestServer()
|
||||||
|
defer authTestServer.Close()
|
||||||
|
|
||||||
|
config := api.NewConfig()
|
||||||
|
config.HTTP.Port = SecurePort3
|
||||||
|
|
||||||
|
u, err := url.Parse(authTestServer.URL)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
|
Bearer: &api.BearerConfig{
|
||||||
|
Cert: ServerCert,
|
||||||
|
Realm: authTestServer.URL + "/auth/token",
|
||||||
|
Service: u.Host,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.HTTP.AllowReadAccess = true
|
||||||
|
c := api.NewController(config)
|
||||||
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
c.Config.Storage.RootDirectory = dir
|
||||||
|
go func() {
|
||||||
|
// this blocks
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait till ready
|
||||||
|
for {
|
||||||
|
_, err := resty.R().Get(BaseURL3)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
_ = c.Server.Shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
blob := []byte("hello, blob!")
|
||||||
|
digest := godigest.FromBytes(blob).String()
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(BaseURL3 + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
|
||||||
|
authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
Get(authorizationHeader.Realm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
var goodToken accessTokenResponse
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Get(BaseURL3 + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
resp, err = resty.R().Post(BaseURL3 + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
|
||||||
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
Get(authorizationHeader.Realm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Post(BaseURL3 + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 202)
|
||||||
|
loc := resp.Header().Get("Location")
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", digest).
|
||||||
|
SetBody(blob).
|
||||||
|
Put(BaseURL3 + loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
|
||||||
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
Get(authorizationHeader.Realm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
SetQueryParam("digest", digest).
|
||||||
|
SetBody(blob).
|
||||||
|
Put(BaseURL3 + loc)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 201)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Get(BaseURL3 + "/v2/" + AuthorizedNamespace + "/tags/list")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
|
||||||
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
Get(authorizationHeader.Realm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Get(BaseURL3 + "/v2/" + AuthorizedNamespace + "/tags/list")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
Post(BaseURL3 + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
|
||||||
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
Get(authorizationHeader.Realm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
var badToken accessTokenResponse
|
||||||
|
err = json.Unmarshal(resp.Body(), &badToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
|
||||||
|
Post(BaseURL3 + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 401)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func makeAuthTestServer() *httptest.Server {
|
func makeAuthTestServer() *httptest.Server {
|
||||||
cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
|
cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
|
||||||
PrivateKeyPath: ServerKey,
|
PrivateKeyPath: ServerKey,
|
||||||
|
|
|
@ -120,6 +120,18 @@ func (rh *RouteHandler) SetupRoutes() {
|
||||||
// @Success 200 {string} string "ok"
|
// @Success 200 {string} string "ok"
|
||||||
func (rh *RouteHandler) CheckVersionSupport(w http.ResponseWriter, r *http.Request) {
|
func (rh *RouteHandler) CheckVersionSupport(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set(DistAPIVersion, "registry/2.0")
|
w.Header().Set(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 {
|
||||||
|
w.Header().Set("WWW-Authenticate", fmt.Sprintf("bearer realm=%s", rh.c.Config.HTTP.Auth.Bearer.Realm))
|
||||||
|
} else {
|
||||||
|
w.Header().Set("WWW-Authenticate", fmt.Sprintf("basic realm=%s", rh.c.Config.HTTP.Realm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WriteData(w, http.StatusOK, "application/json", []byte{})
|
WriteData(w, http.StatusOK, "application/json", []byte{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue