0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-03-18 02:22:53 -05:00

fix(sync): fixed harbor authentication issues on _catalog endpoint (#2891)

Signed-off-by: Petu Eusebiu <petu.eusebiu@gmail.com>
This commit is contained in:
peusebiu 2025-01-30 19:40:24 +02:00 committed by GitHub
parent 67231230e5
commit 90e1393585
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 64 additions and 41 deletions

View file

@ -174,4 +174,5 @@ var (
ErrInvalidSearchQuery = errors.New("invalid search query")
ErrImageNotFound = errors.New("image not found")
ErrAmbiguousInput = errors.New("input is not specific enough")
ErrReceivedUnexpectedAuthHeader = errors.New("received unexpected www-authenticate header")
)

View file

@ -160,12 +160,12 @@ func (httpClient *Client) Ping() bool {
defer cancel()
//nolint: bodyclose
resp, _, err := httpClient.get(ctx, pingURL.String(), false)
resp, _, err := httpClient.get(ctx, pingURL.String(), "", false)
if err != nil {
return false
}
httpClient.getAuthType(resp)
httpClient.authType = getAuthType(resp)
if resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusForbidden {
return true
@ -220,21 +220,6 @@ func (httpClient *Client) MakeGetRequest(ctx context.Context, resultPtr interfac
return body, resp.Header, resp.StatusCode, err
}
func (httpClient *Client) getAuthType(resp *http.Response) {
authHeader := resp.Header.Get("www-authenticate")
authHeaderLower := strings.ToLower(authHeader)
//nolint: gocritic
if strings.Contains(authHeaderLower, "bearer") {
httpClient.authType = tokenAuth
} else if strings.Contains(authHeaderLower, "basic") {
httpClient.authType = basicAuth
} else {
httpClient.authType = noneAuth
}
}
func (httpClient *Client) setupAuth(req *http.Request, namespace string) error {
if httpClient.authType == tokenAuth {
token, err := httpClient.getToken(req.URL.String(), namespace)
@ -254,13 +239,19 @@ func (httpClient *Client) setupAuth(req *http.Request, namespace string) error {
return nil
}
func (httpClient *Client) get(ctx context.Context, url string, setAuth bool) (*http.Response, []byte, error) {
func (httpClient *Client) get(ctx context.Context, url string, mediaType string,
setBasicAuth bool,
) (*http.Response, []byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) //nolint
if err != nil {
return nil, nil, err
}
if setAuth && httpClient.config.Username != "" && httpClient.config.Password != "" {
if mediaType != "" {
req.Header.Set("Accept", mediaType)
}
if setBasicAuth && httpClient.config.Username != "" && httpClient.config.Password != "" {
req.SetBasicAuth(httpClient.config.Username, httpClient.config.Password)
}
@ -298,7 +289,16 @@ func (httpClient *Client) makeAndDoRequest(method, mediaType, namespace, urlStr
return nil, nil, err
}
if err := httpClient.setupAuth(req, namespace); err != nil {
err = httpClient.setupAuth(req, namespace)
if err != nil {
// harbor catalog requests return basicAuth by default, even if bearer is used on the rest of endpoints.
if errors.Is(err, zerr.ErrReceivedUnexpectedAuthHeader) {
httpClient.log.Err(err).Msg("expected bearer auth header, received basic, retrying with basic auth...")
// try with basic auth
return httpClient.get(context.Background(), urlStr, mediaType, true)
}
return nil, nil, err
}
@ -311,25 +311,27 @@ func (httpClient *Client) makeAndDoRequest(method, mediaType, namespace, urlStr
return nil, nil, err
}
// let's retry one time if we get an insufficient_scope error
if ok, challengeParams := needsRetryWithUpdatedScope(err, resp); ok {
var tokenURL *url.URL
if httpClient.authType == tokenAuth {
// let's retry one time if we get an insufficient_scope error
if ok, challengeParams := needsRetryWithUpdatedScope(err, resp); ok {
var tokenURL *url.URL
var token *bearerToken
var token *bearerToken
tokenURL, err = getTokenURLFromChallengeParams(challengeParams, httpClient.config.Username)
if err != nil {
return nil, nil, err
tokenURL, err = getTokenURLFromChallengeParams(challengeParams, httpClient.config.Username)
if err != nil {
return nil, nil, err
}
token, err = httpClient.getTokenFromURL(tokenURL.String(), namespace)
if err != nil {
return nil, nil, err
}
req.Header.Set("Authorization", "Bearer "+token.Token)
resp, body, err = httpClient.doRequest(req)
}
token, err = httpClient.getTokenFromURL(tokenURL.String(), namespace)
if err != nil {
return nil, nil, err
}
req.Header.Set("Authorization", "Bearer "+token.Token)
resp, body, err = httpClient.doRequest(req)
}
return resp, body, err
@ -337,7 +339,7 @@ func (httpClient *Client) makeAndDoRequest(method, mediaType, namespace, urlStr
func (httpClient *Client) getTokenFromURL(urlStr, namespace string) (*bearerToken, error) {
//nolint: bodyclose
resp, body, err := httpClient.get(context.Background(), urlStr, true)
resp, body, err := httpClient.get(context.Background(), urlStr, "", true)
if err != nil {
return nil, err
}
@ -366,12 +368,12 @@ func (httpClient *Client) getToken(urlStr, namespace string) (*bearerToken, erro
}
//nolint: bodyclose
resp, _, err := httpClient.get(context.Background(), urlStr, false)
resp, _, err := httpClient.get(context.Background(), urlStr, "", false)
if err != nil {
return nil, err
}
challengeParams, err := parseAuthHeader(resp)
challengeParams, err := parseBearerAuthHeader(resp)
if err != nil {
return nil, err
}
@ -384,6 +386,21 @@ func (httpClient *Client) getToken(urlStr, namespace string) (*bearerToken, erro
return httpClient.getTokenFromURL(tokenURL.String(), namespace)
}
func getAuthType(resp *http.Response) authType {
authHeader := resp.Header.Get("www-authenticate")
authHeaderLower := strings.ToLower(authHeader)
//nolint: gocritic
if strings.Contains(authHeaderLower, "bearer") {
return tokenAuth
} else if strings.Contains(authHeaderLower, "basic") {
return basicAuth
} else {
return noneAuth
}
}
func newBearerToken(blob []byte) (*bearerToken, error) {
token := new(bearerToken)
if err := json.Unmarshal(blob, &token); err != nil {
@ -426,7 +443,7 @@ func getTokenURLFromChallengeParams(params challengeParams, account string) (*ur
return parsedRealm, nil
}
func parseAuthHeader(resp *http.Response) (challengeParams, error) {
func parseBearerAuthHeader(resp *http.Response) (challengeParams, error) {
authHeader := resp.Header.Get("www-authenticate")
authHeaderSlice := strings.Split(authHeader, ",")
@ -434,6 +451,11 @@ func parseAuthHeader(resp *http.Response) (challengeParams, error) {
params := challengeParams{}
for _, elem := range authHeaderSlice {
// expected bearer auth header
if strings.Contains(strings.ToLower(elem), "basic") {
return params, zerr.ErrReceivedUnexpectedAuthHeader
}
if strings.Contains(strings.ToLower(elem), "bearer") {
elem = strings.Split(elem, " ")[1]
}
@ -469,7 +491,7 @@ func parseAuthHeader(resp *http.Response) (challengeParams, error) {
func needsRetryWithUpdatedScope(err error, resp *http.Response) (bool, challengeParams) {
params := challengeParams{}
if err == nil && resp.StatusCode == http.StatusUnauthorized {
params, err = parseAuthHeader(resp)
params, err = parseBearerAuthHeader(resp)
if err != nil {
return false, params
}