0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-02-17 23:45:36 -05:00

fix(bearer): fixed /v2/ route not implementing token spec (#2176)

We use chartmuseum lib for handling bearer requests, which is not
implementing the token spec, mainly it expects "scope" parameter
to be given on every request, even for /v2/ route which doesn't represent
a resource.

Handle this /v2/ route inside our code.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
peusebiu 2024-01-22 19:15:27 +02:00 committed by GitHub
parent ed6be0580e
commit e9ab520905
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 78 additions and 19 deletions

View file

@ -11,6 +11,7 @@ import (
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
@ -450,13 +451,55 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
action = auth.PushAction
}
permissions, err := authorizer.Authorize(header, action, name)
if err != nil {
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
response.Header().Set("Content-Type", "application/json")
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
var permissions *auth.Permission
return
// Empty scope should be allowed according to the distribution auth spec
// This is only necessary for the bearer auth type
if request.RequestURI == "/v2/" && authorizer.Type == auth.BearerAuthAuthorizerType {
if header == "" {
// first request that clients make (without any header)
WWWAuthenticateHeader := fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"\"",
authorizer.Realm, authorizer.Service)
permissions = &auth.Permission{
// challenge for the client to use to authenticate to /v2/
WWWAuthenticateHeader: WWWAuthenticateHeader,
Allowed: false,
}
} else {
// subsequent requests with token on /v2/
bearerTokenMatch := regexp.MustCompile("(?i)bearer (.*)")
signedString := bearerTokenMatch.ReplaceAllString(header, "$1")
// If the token is valid, our job is done
// Since this is the /v2 base path and we didn't pass a scope to the auth header in the previous step
// there is no access check to enforce
_, err := authorizer.TokenDecoder.DecodeToken(signedString)
if err != nil {
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
response.Header().Set("Content-Type", "application/json")
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
return
}
permissions = &auth.Permission{
Allowed: true,
}
}
} else {
var err error
// subsequent requests with token on /v2/<resource>/
permissions, err = authorizer.Authorize(header, action, name)
if err != nil {
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
response.Header().Set("Content-Type", "application/json")
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
return
}
}
if !permissions.Allowed {

View file

@ -2916,7 +2916,6 @@ func TestBearerAuth(t *testing.T) {
authorizationHeader := authutils.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)
@ -2932,6 +2931,14 @@ func TestBearerAuth(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// trigger decode error
resp, err = resty.R().
SetHeader("Authorization", fmt.Sprintf("Bearer %s", "invalidToken")).
Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
resp, err = resty.R().SetHeader("Authorization",
fmt.Sprintf("Bearer %s", goodToken.AccessToken)).Options(baseURL + "/v2/")
So(err, ShouldBeNil)

View file

@ -110,6 +110,10 @@ func getExpiredSessions(dir string) ([]string, error) {
return nil
})
if os.IsNotExist(err) {
return sessions, nil
}
return sessions, err
}

View file

@ -36,20 +36,25 @@ func MakeAuthTestServer(serverKey string, unauthorizedNamespace string) *httptes
}
authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
var access []auth.AccessEntry
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,
},
if 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)