Skip to content

Commit

Permalink
fix(sync): fixed harbor authentication issues on _catalog endpoint (#…
Browse files Browse the repository at this point in the history
…2891)

Signed-off-by: Petu Eusebiu <[email protected]>
  • Loading branch information
eusebiu-constantin-petu-dbk authored Jan 30, 2025
1 parent 6723123 commit 90e1393
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 39 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
100 changes: 61 additions & 39 deletions pkg/extensions/sync/httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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
}

Expand All @@ -311,33 +311,35 @@ 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
}
token, err = httpClient.getTokenFromURL(tokenURL.String(), namespace)
if err != nil {
return nil, nil, err
}

req.Header.Set("Authorization", "Bearer "+token.Token)
req.Header.Set("Authorization", "Bearer "+token.Token)

resp, body, err = httpClient.doRequest(req)
resp, body, err = httpClient.doRequest(req)
}
}

return resp, body, err
}

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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -426,14 +443,19 @@ 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, ",")

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]
}
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 90e1393

Please sign in to comment.