From 0ac78941abfed981a47f263fa59931e9123f73a1 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Thu, 28 Sep 2023 10:26:37 +0200 Subject: [PATCH] fix(registry): address anonymous pull issue 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 Signed-off-by: Hidde Beydals (cherry picked from commit fe4c01f6241a8de566a6fc94cb6d1e5b5eb273d6) --- pkg/registry/client.go | 7 +----- pkg/registry/util.go | 19 ++++++++++++++++ pkg/registry/util_test.go | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 0dfa6926f..0dc6390a2 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -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)} diff --git a/pkg/registry/util.go b/pkg/registry/util.go index ca93297e6..6fb1d0cda 100644 --- a/pkg/registry/util.go +++ b/pkg/registry/util.go @@ -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))) +} diff --git a/pkg/registry/util_test.go b/pkg/registry/util_test.go index f08c1fef1..f641801fe 100644 --- a/pkg/registry/util_test.go +++ b/pkg/registry/util_test.go @@ -17,6 +17,7 @@ limitations under the License. package registry // import "helm.sh/helm/v3/pkg/registry" import ( + "net/http" "reflect" "testing" "time" @@ -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) + } + }) + } +}