From 9b4a6f129517e353de70b824d78c755605eb84f6 Mon Sep 17 00:00:00 2001 From: Jacob MacElroy Date: Tue, 26 Oct 2021 12:57:39 -0600 Subject: [PATCH 1/3] Generate token options with each scope as a separate string. Currently scopes added to token options are added with all scopes included in space delimited string. This changes it so that each scope is added to the string slice as a separate string. This seems to be the desire behavior based on the fact that a string slice is used and the usage of this function in github.com/moby/buildkit. Signed-off-by: Jacob MacElroy --- remotes/docker/auth/fetch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remotes/docker/auth/fetch.go b/remotes/docker/auth/fetch.go index 8b0a87e75..02b995224 100644 --- a/remotes/docker/auth/fetch.go +++ b/remotes/docker/auth/fetch.go @@ -58,7 +58,7 @@ func GenerateTokenOptions(ctx context.Context, host, username, secret string, c scope, ok := c.Parameters["scope"] if ok { - to.Scopes = append(to.Scopes, scope) + to.Scopes = append(to.Scopes, strings.Split(scope, " ")...) } else { log.G(ctx).WithField("host", host).Debug("no scope specified for token auth challenge") } From 7438edc7ed976fd38d3b4051acf45874ec611330 Mon Sep 17 00:00:00 2001 From: Jacob MacElroy Date: Tue, 26 Oct 2021 14:05:17 -0600 Subject: [PATCH 2/3] Adding tests for GenerateTokenOptions Test are being added for GenerateTokenOptions to cover multiple scope cases and the error cases handled. Signed-off-by: Jacob MacElroy --- remotes/docker/auth/fetch_test.go | 114 ++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 remotes/docker/auth/fetch_test.go diff --git a/remotes/docker/auth/fetch_test.go b/remotes/docker/auth/fetch_test.go new file mode 100644 index 000000000..087bd62d6 --- /dev/null +++ b/remotes/docker/auth/fetch_test.go @@ -0,0 +1,114 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package auth + +import ( + "context" + "reflect" + "strings" + "testing" +) + +func TestGenerateTokenOptions(t *testing.T) { + for _, tc := range []struct { + name string + realm string + service string + username string + secret string + scope string + }{ + { + name: "MultipleScopes", + realm: "https://test-realm.com", + service: "registry-service", + username: "username", + secret: "secret", + scope: "repository:foo/bar:pull repository:foo/bar:pull,push", + }, + { + name: "SingleScope", + realm: "https://test-realm.com", + service: "registry-service", + username: "username", + secret: "secret", + scope: "repository:foo/bar:pull", + }, + { + name: "NoScope", + realm: "https://test-realm.com", + service: "registry-service", + username: "username", + secret: "secret", + }, + } { + t.Run(tc.name, func(t *testing.T) { + c := Challenge{ + Scheme: BearerAuth, + Parameters: map[string]string{ + "realm": tc.realm, + "service": tc.service, + "scope": tc.scope, + }, + } + options, err := GenerateTokenOptions(context.Background(), "host", tc.username, tc.secret, c) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + + expected := TokenOptions{ + Realm: tc.realm, + Service: tc.service, + Scopes: strings.Split(tc.scope, " "), + Username: tc.username, + Secret: tc.secret, + } + if !reflect.DeepEqual(options, expected) { + t.Fatalf("expected %v, but got %v", expected, options) + } + }) + } + + t.Run("MissingRealm", func(t *testing.T) { + c := Challenge{ + Scheme: BearerAuth, + Parameters: map[string]string{ + "service": "service", + "scope": "repository:foo/bar:pull,push", + }, + } + _, err := GenerateTokenOptions(context.Background(), "host", "username", "secret", c) + if err == nil { + t.Fatal("expected an err and got nil") + } + }) + + t.Run("RealmParseError", func(t *testing.T) { + c := Challenge{ + Scheme: BearerAuth, + Parameters: map[string]string{ + "realm": "127.0.0.1:8080", + "service": "service", + "scope": "repository:foo/bar:pull,push", + }, + } + _, err := GenerateTokenOptions(context.Background(), "host", "username", "secret", c) + if err == nil { + t.Fatal("expected an err and got nil") + } + }) +} From 88fc5cf2d00239d235e4efaee18e2628d97e8e30 Mon Sep 17 00:00:00 2001 From: Jacob MacElroy Date: Wed, 27 Oct 2021 11:09:32 -0600 Subject: [PATCH 3/3] Adding scope tests for ParseAuthHeader Signed-off-by: Jacob MacElroy --- remotes/docker/auth/parse_test.go | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 remotes/docker/auth/parse_test.go diff --git a/remotes/docker/auth/parse_test.go b/remotes/docker/auth/parse_test.go new file mode 100644 index 000000000..614b6c6ea --- /dev/null +++ b/remotes/docker/auth/parse_test.go @@ -0,0 +1,71 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package auth + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestParseAuthHeader(t *testing.T) { + headerTemplate := `Bearer realm="%s",service="%s",scope="%s"` + + for _, tc := range []struct { + name string + realm string + service string + scope string + }{ + { + name: "SingleScope", + realm: "https://auth.docker.io/token", + service: "registry.docker.io", + scope: "repository:foo/bar:pull,push", + }, + { + name: "MultipleScopes", + realm: "https://auth.docker.io/token", + service: "registry.docker.io", + scope: "repository:foo/bar:pull,push repository:foo/baz:pull repository:foo/foo:push", + }, + } { + t.Run(tc.name, func(t *testing.T) { + expected := []Challenge{ + { + Scheme: BearerAuth, + Parameters: map[string]string{ + "realm": tc.realm, + "service": tc.service, + "scope": tc.scope, + }, + }, + } + + hdr := http.Header{ + http.CanonicalHeaderKey("WWW-Authenticate"): []string{fmt.Sprintf( + headerTemplate, tc.realm, tc.service, tc.scope, + )}, + } + actual := ParseAuthHeader(hdr) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, but got %v", expected, actual) + } + }) + } +}