mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
fix(mgmt): skip bearer authn for mgmt route (#1267)
Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
parent
150ee88945
commit
4d0bbf1e00
5 changed files with 269 additions and 85 deletions
|
@ -55,7 +55,18 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||||
}
|
}
|
||||||
vars := mux.Vars(request)
|
vars := mux.Vars(request)
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
|
||||||
|
// we want to bypass auth for mgmt route
|
||||||
|
isMgmtRequested := request.RequestURI == constants.FullMgmtPrefix
|
||||||
|
|
||||||
header := request.Header.Get("Authorization")
|
header := request.Header.Get("Authorization")
|
||||||
|
|
||||||
|
if (header == "" || header == "Basic Og==") && isMgmtRequested {
|
||||||
|
next.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
action := auth.PullAction
|
action := auth.PullAction
|
||||||
if m := request.Method; m != http.MethodGet && m != http.MethodHead {
|
if m := request.Method; m != http.MethodGet && m != http.MethodHead {
|
||||||
action = auth.PushAction
|
action = auth.PushAction
|
||||||
|
|
|
@ -19,15 +19,12 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chartmuseum/auth"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
vldap "github.com/nmcclain/ldap"
|
vldap "github.com/nmcclain/ldap"
|
||||||
notreg "github.com/notaryproject/notation-go/registry"
|
notreg "github.com/notaryproject/notation-go/registry"
|
||||||
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
|
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
|
||||||
|
@ -72,18 +69,6 @@ const (
|
||||||
AuthorizationAllRepos = "**"
|
AuthorizationAllRepos = "**"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
|
||||||
accessTokenResponse struct {
|
|
||||||
AccessToken string `json:"access_token"` //nolint:tagliatelle // token format
|
|
||||||
}
|
|
||||||
|
|
||||||
authHeader struct {
|
|
||||||
Realm string
|
|
||||||
Service string
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func getCredString(username, password string) string {
|
func getCredString(username, password string) string {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1892,7 +1877,7 @@ func TestLDAPFailures(t *testing.T) {
|
||||||
|
|
||||||
func TestBearerAuth(t *testing.T) {
|
func TestBearerAuth(t *testing.T) {
|
||||||
Convey("Make a new controller", t, func() {
|
Convey("Make a new controller", t, func() {
|
||||||
authTestServer := makeAuthTestServer()
|
authTestServer := test.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
|
||||||
defer authTestServer.Close()
|
defer authTestServer.Close()
|
||||||
|
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
|
@ -1925,7 +1910,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader := test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -1933,7 +1918,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
var goodToken accessTokenResponse
|
var goodToken test.AccessTokenResponse
|
||||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
@ -1955,7 +1940,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -1984,7 +1969,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2013,7 +1998,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2037,7 +2022,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2045,7 +2030,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
var badToken accessTokenResponse
|
var badToken test.AccessTokenResponse
|
||||||
err = json.Unmarshal(resp.Body(), &badToken)
|
err = json.Unmarshal(resp.Body(), &badToken)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
@ -2060,7 +2045,7 @@ func TestBearerAuth(t *testing.T) {
|
||||||
|
|
||||||
func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
Convey("Make a new controller", t, func() {
|
Convey("Make a new controller", t, func() {
|
||||||
authTestServer := makeAuthTestServer()
|
authTestServer := test.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
|
||||||
defer authTestServer.Close()
|
defer authTestServer.Close()
|
||||||
|
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
|
@ -2101,7 +2086,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader := test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2109,7 +2094,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
var goodToken accessTokenResponse
|
var goodToken test.AccessTokenResponse
|
||||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
@ -2125,7 +2110,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2154,7 +2139,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2183,7 +2168,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2207,7 +2192,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
authorizationHeader = test.ParseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
||||||
resp, err = resty.R().
|
resp, err = resty.R().
|
||||||
SetQueryParam("service", authorizationHeader.Service).
|
SetQueryParam("service", authorizationHeader.Service).
|
||||||
SetQueryParam("scope", authorizationHeader.Scope).
|
SetQueryParam("scope", authorizationHeader.Scope).
|
||||||
|
@ -2215,7 +2200,7 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
var badToken accessTokenResponse
|
var badToken test.AccessTokenResponse
|
||||||
err = json.Unmarshal(resp.Body(), &badToken)
|
err = json.Unmarshal(resp.Body(), &badToken)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
@ -2228,60 +2213,6 @@ func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAuthTestServer() *httptest.Server {
|
|
||||||
cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
|
|
||||||
PrivateKeyPath: ServerKey,
|
|
||||||
Audience: "Zot Registry",
|
|
||||||
Issuer: "Zot",
|
|
||||||
AddKIDHeader: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
||||||
scope := request.URL.Query().Get("scope")
|
|
||||||
parts := strings.Split(scope, ":")
|
|
||||||
name := parts[1]
|
|
||||||
actions := strings.Split(parts[2], ",")
|
|
||||||
if name == UnauthorizedNamespace {
|
|
||||||
actions = []string{}
|
|
||||||
}
|
|
||||||
access := []auth.AccessEntry{
|
|
||||||
{
|
|
||||||
Name: name,
|
|
||||||
Type: "repository",
|
|
||||||
Actions: actions,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
token, err := cmTokenGenerator.GenerateToken(access, time.Minute*1)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
response.Header().Set("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(response, `{"access_token": "%s"}`, token)
|
|
||||||
}))
|
|
||||||
|
|
||||||
return authTestServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBearerAuthHeader(authHeaderRaw string) *authHeader {
|
|
||||||
re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`)
|
|
||||||
matches := re.FindAllStringSubmatch(authHeaderRaw, -1)
|
|
||||||
matchmap := make(map[string]string)
|
|
||||||
|
|
||||||
for i := 0; i < len(matches); i++ {
|
|
||||||
matchmap[matches[i][1]] = matches[i][2]
|
|
||||||
}
|
|
||||||
|
|
||||||
var h authHeader
|
|
||||||
if err := mapstructure.Decode(matchmap, &h); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &h
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthorizationWithBasicAuth(t *testing.T) {
|
func TestAuthorizationWithBasicAuth(t *testing.T) {
|
||||||
Convey("Make a new controller", t, func() {
|
Convey("Make a new controller", t, func() {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
|
|
|
@ -5,7 +5,9 @@ package extensions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -21,6 +23,11 @@ import (
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServerCert = "../../test/data/server.cert"
|
||||||
|
ServerKey = "../../test/data/server.key"
|
||||||
|
)
|
||||||
|
|
||||||
func TestEnableExtension(t *testing.T) {
|
func TestEnableExtension(t *testing.T) {
|
||||||
Convey("Verify log if sync disabled in config", t, func() {
|
Convey("Verify log if sync disabled in config", t, func() {
|
||||||
globalDir := t.TempDir()
|
globalDir := t.TempDir()
|
||||||
|
@ -509,3 +516,153 @@ func TestMgmtExtension(t *testing.T) {
|
||||||
So(string(data), ShouldContainSubstring, "setting up mgmt routes")
|
So(string(data), ShouldContainSubstring, "setting up mgmt routes")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMgmtWithBearer(t *testing.T) {
|
||||||
|
Convey("Make a new controller", t, func() {
|
||||||
|
authorizedNamespace := "allowedrepo"
|
||||||
|
unauthorizedNamespace := "notallowedrepo"
|
||||||
|
authTestServer := test.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
|
||||||
|
defer authTestServer.Close()
|
||||||
|
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
aurl, err := url.Parse(authTestServer.URL)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
conf.HTTP.Auth = &config.AuthConfig{
|
||||||
|
Bearer: &config.BearerConfig{
|
||||||
|
Cert: ServerCert,
|
||||||
|
Realm: authTestServer.URL + "/auth/token",
|
||||||
|
Service: aurl.Host,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultValue := true
|
||||||
|
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{}
|
||||||
|
conf.Extensions.Mgmt = &extconf.MgmtConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{
|
||||||
|
Enable: &defaultValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.Storage.RootDirectory = t.TempDir()
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
authorizationHeader := test.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, http.StatusOK)
|
||||||
|
var goodToken test.AccessTokenResponse
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Get(baseURL + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
|
||||||
|
resp, err = resty.R().SetHeader("Authorization",
|
||||||
|
fmt.Sprintf("Bearer %s", goodToken.AccessToken)).Options(baseURL + "/v2/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
|
||||||
|
|
||||||
|
resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
authorizationHeader = test.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, http.StatusOK)
|
||||||
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
||||||
|
Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
authorizationHeader = test.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, http.StatusOK)
|
||||||
|
var badToken test.AccessTokenResponse
|
||||||
|
err = json.Unmarshal(resp.Body(), &badToken)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
|
||||||
|
Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// test mgmt route
|
||||||
|
resp, err = resty.R().Get(baseURL + constants.FullMgmtPrefix)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
|
||||||
|
mgmtResp := extensions.StrippedConfig{}
|
||||||
|
err = json.Unmarshal(resp.Body(), &mgmtResp)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
|
||||||
|
So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
|
||||||
|
So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
|
||||||
|
|
||||||
|
resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmtPrefix)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||||
|
|
||||||
|
mgmtResp = extensions.StrippedConfig{}
|
||||||
|
err = json.Unmarshal(resp.Body(), &mgmtResp)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
|
||||||
|
So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
|
||||||
|
So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
|
||||||
|
So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
79
pkg/test/bearer.go
Normal file
79
pkg/test/bearer.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/chartmuseum/auth"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AccessTokenResponse struct {
|
||||||
|
AccessToken string `json:"access_token"` //nolint:tagliatelle // token format
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthHeader struct {
|
||||||
|
Realm string
|
||||||
|
Service string
|
||||||
|
Scope string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeAuthTestServer(serverKey string, unauthorizedNamespace string) *httptest.Server {
|
||||||
|
cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
|
||||||
|
PrivateKeyPath: serverKey,
|
||||||
|
Audience: "Zot Registry",
|
||||||
|
Issuer: "Zot",
|
||||||
|
AddKIDHeader: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
scope := request.URL.Query().Get("scope")
|
||||||
|
parts := strings.Split(scope, ":")
|
||||||
|
name := parts[1]
|
||||||
|
actions := strings.Split(parts[2], ",")
|
||||||
|
if name == unauthorizedNamespace {
|
||||||
|
actions = []string{}
|
||||||
|
}
|
||||||
|
access := []auth.AccessEntry{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
Type: "repository",
|
||||||
|
Actions: actions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
token, err := cmTokenGenerator.GenerateToken(access, time.Minute*1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
response.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(response, `{"access_token": "%s"}`, token)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return authTestServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseBearerAuthHeader(authHeaderRaw string) *AuthHeader {
|
||||||
|
re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`)
|
||||||
|
matches := re.FindAllStringSubmatch(authHeaderRaw, -1)
|
||||||
|
matchmap := make(map[string]string)
|
||||||
|
|
||||||
|
for i := 0; i < len(matches); i++ {
|
||||||
|
matchmap[matches[i][1]] = matches[i][2]
|
||||||
|
}
|
||||||
|
|
||||||
|
var h AuthHeader
|
||||||
|
if err := mapstructure.Decode(matchmap, &h); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &h
|
||||||
|
}
|
|
@ -1336,3 +1336,9 @@ func TestWriteImageToFileSystem(t *testing.T) {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBearerServer(t *testing.T) {
|
||||||
|
Convey("test MakeAuthTestServer() no serve key", t, func() {
|
||||||
|
So(func() { test.MakeAuthTestServer("", "") }, ShouldPanic)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue