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
4 changed files with 78 additions and 19 deletions
+49 -6
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 {
+8 -1
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)
+4
View File
@@ -110,6 +110,10 @@ func getExpiredSessions(dir string) ([]string, error) {
return nil
})
if os.IsNotExist(err) {
return sessions, nil
}
return sessions, err
}