Skip to content

Commit

Permalink
fix(registry): address anonymous pull issue
Browse files Browse the repository at this point in the history
The assumption that either a username and/or password OR an error is
returned appears to be wrong, and results in an error later on which
looks something like the following:

```
failed to authorize: failed to fetch anonymous token: unexpected status
from GET request to https://auth.docker.io/token?scope=repository%3AXXX%2FYYY%3Apull&service=registry.docker.io:
401 Unauthorized
```

To mitigate this, confirm we actually have one of the values before
setting the `Authorization` header.

Co-authored-by: Joe Julian <me@joejulian.name>
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
  • Loading branch information
hiddeco and joejulian committed Sep 29, 2023
1 parent 0e72b64 commit fe4c01f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
7 changes: 1 addition & 6 deletions pkg/registry/client.go
Expand Up @@ -105,12 +105,7 @@ func NewClient(options ...ClientOption) (*Client, error) {
if err != nil {
return nil, errors.New("unable to retrieve credentials")
}
// A blank returned username and password value is a bearer token
if username == "" && password != "" {
headers.Set("Authorization", fmt.Sprintf("Bearer %s", password))
} else {
headers.Set("Authorization", fmt.Sprintf("Basic %s", basicAuth(username, password)))
}
authHeader(username, password, &headers)
}

opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)}
Expand Down
19 changes: 19 additions & 0 deletions pkg/registry/util.go
Expand Up @@ -256,3 +256,22 @@ func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}

// authHeader generates an HTTP authorization header based on the provided
// username and password and sets it in the provided HTTP headers pointer.
//
// If both username and password are empty, no header is set.
// If only the password is provided, a "Bearer" token is created and set in
// the Authorization header.
// If both username and password are provided, a "Basic" authentication token
// is created using the basicAuth function, and set in the Authorization header.
func authHeader(username, password string, headers *http.Header) {
if username == "" && password == "" {
return
}
if username == "" {
headers.Set("Authorization", fmt.Sprintf("Bearer %s", password))
return
}
headers.Set("Authorization", fmt.Sprintf("Basic %s", basicAuth(username, password)))
}
47 changes: 47 additions & 0 deletions pkg/registry/util_test.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package registry // import "helm.sh/helm/v3/pkg/registry"

import (
"net/http"
"reflect"
"testing"
"time"
Expand Down Expand Up @@ -266,3 +267,49 @@ func Test_basicAuth(t *testing.T) {
})
}
}

func Test_authHeader(t *testing.T) {
tests := []struct {
name string
username string
password string
expectedHeader http.Header
}{
{
name: "basic login header with username and password",
username: "admin",
password: "passw0rd",
expectedHeader: func() http.Header {
header := http.Header{}
header.Set("Authorization", "Basic YWRtaW46cGFzc3cwcmQ=")
return header
}(),
},
{
name: "bearer login header with no username and password",
username: "",
password: "hunter2",
expectedHeader: func() http.Header {
header := http.Header{}
header.Set("Authorization", "Bearer hunter2")
return header
}(),
},
{
name: "no change in header with neither username nor password",
username: "",
password: "",
expectedHeader: http.Header{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := &http.Header{}
authHeader(tt.username, tt.password, got)
if !reflect.DeepEqual(*got, tt.expectedHeader) {
t.Errorf("authHeader got %#v wanted %#v", *got, tt.expectedHeader)
}
})
}
}

0 comments on commit fe4c01f

Please sign in to comment.