remotes: add handling for missing basic auth credentials
When a credential handler is provided but no basic auth credentials are provided, handle the error specifically rather than treating the credentials as not implemented. This allows a clearer error to be provided to users rather than a confusing not implemented error or generic unauthorized error. Add unit tests for the basic auth case. Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
parent
15bf23df09
commit
51c9ffe468
@ -186,15 +186,15 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if username != "" && secret != "" {
|
if username == "" || secret == "" {
|
||||||
common := auth.TokenOptions{
|
return fmt.Errorf("%w: no basic auth credentials", ErrInvalidAuthorization)
|
||||||
Username: username,
|
|
||||||
Secret: secret,
|
|
||||||
}
|
|
||||||
|
|
||||||
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, auth.TokenOptions{
|
||||||
|
Username: username,
|
||||||
|
Secret: secret,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to find supported auth scheme: %w", errdefs.ErrNotImplemented)
|
return fmt.Errorf("failed to find supported auth scheme: %w", errdefs.ErrNotImplemented)
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
"github.com/containerd/containerd/remotes/docker/auth"
|
"github.com/containerd/containerd/remotes/docker/auth"
|
||||||
|
remoteerrors "github.com/containerd/containerd/remotes/errors"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go"
|
specs "github.com/opencontainers/image-spec/specs-go"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@ -215,6 +216,14 @@ func TestPostBasicAuthTokenResolver(t *testing.T) {
|
|||||||
runBasicTest(t, "testname", withTokenServer(th, creds))
|
runBasicTest(t, "testname", withTokenServer(th, creds))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthResolver(t *testing.T) {
|
||||||
|
creds := func(string) (string, string, error) {
|
||||||
|
return "totallyvaliduser", "totallyvalidpassword", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
runBasicTest(t, "testname", withBasicAuthServer(creds))
|
||||||
|
}
|
||||||
|
|
||||||
func TestBadTokenResolver(t *testing.T) {
|
func TestBadTokenResolver(t *testing.T) {
|
||||||
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
@ -236,7 +245,7 @@ func TestBadTokenResolver(t *testing.T) {
|
|||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
resolver := NewResolver(ro)
|
resolver := NewResolver(ro)
|
||||||
image := fmt.Sprintf("%s/doesntmatter:sometatg", base)
|
image := fmt.Sprintf("%s/doesntmatter:sometag", base)
|
||||||
|
|
||||||
_, _, err := resolver.Resolve(ctx, image)
|
_, _, err := resolver.Resolve(ctx, image)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -247,6 +256,59 @@ func TestBadTokenResolver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMissingBasicAuthResolver(t *testing.T) {
|
||||||
|
creds := func(string) (string, string, error) {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
h := newContent(ocispec.MediaTypeImageManifest, []byte("not anything parse-able"))
|
||||||
|
|
||||||
|
base, ro, close := withBasicAuthServer(creds)(logHandler{t, h})
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
resolver := NewResolver(ro)
|
||||||
|
image := fmt.Sprintf("%s/doesntmatter:sometag", base)
|
||||||
|
|
||||||
|
_, _, err := resolver.Resolve(ctx, image)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error getting token with inssufficient scope")
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrInvalidAuthorization) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "no basic auth credentials") {
|
||||||
|
t.Fatalf("expected \"no basic auth credentials\" message, got %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrongBasicAuthResolver(t *testing.T) {
|
||||||
|
creds := func(string) (string, string, error) {
|
||||||
|
return "totallyvaliduser", "definitelythewrongpassword", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
h := newContent(ocispec.MediaTypeImageManifest, []byte("not anything parse-able"))
|
||||||
|
|
||||||
|
base, ro, close := withBasicAuthServer(creds)(logHandler{t, h})
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
resolver := NewResolver(ro)
|
||||||
|
image := fmt.Sprintf("%s/doesntmatter:sometag", base)
|
||||||
|
|
||||||
|
_, _, err := resolver.Resolve(ctx, image)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error getting token with inssufficient scope")
|
||||||
|
}
|
||||||
|
var rerr remoteerrors.ErrUnexpectedStatus
|
||||||
|
if !errors.As(err, &rerr) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if rerr.StatusCode != 403 {
|
||||||
|
t.Fatalf("expected 403 status code, got %d", rerr.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHostFailureFallbackResolver(t *testing.T) {
|
func TestHostFailureFallbackResolver(t *testing.T) {
|
||||||
sf := func(h http.Handler) (string, ResolverOptions, func()) {
|
sf := func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
s := httptest.NewServer(h)
|
s := httptest.NewServer(h)
|
||||||
@ -549,6 +611,37 @@ func withTokenServer(th http.Handler, creds func(string) (string, string, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withBasicAuthServer(creds func(string) (string, string, error)) func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
|
return func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
|
// Wrap with basic auth
|
||||||
|
wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
user, password, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
if user != "totallyvaliduser" || password != "totallyvalidpassword" {
|
||||||
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
|
rw.Write([]byte(`{"errors":[{"code":"DENIED"}]}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
authHeader := "Basic realm=\"testserver\""
|
||||||
|
rw.Header().Set("WWW-Authenticate", authHeader)
|
||||||
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeHTTP(rw, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
base, options, close := tlsServer(wrapped)
|
||||||
|
options.Hosts = ConfigureDefaultRegistries(
|
||||||
|
WithClient(options.Client),
|
||||||
|
WithAuthorizer(NewDockerAuthorizer(
|
||||||
|
WithAuthCreds(creds),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
return base, options, close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func tlsServer(h http.Handler) (string, ResolverOptions, func()) {
|
func tlsServer(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
s := httptest.NewUnstartedServer(h)
|
s := httptest.NewUnstartedServer(h)
|
||||||
s.StartTLS()
|
s.StartTLS()
|
||||||
@ -564,6 +657,7 @@ func tlsServer(h http.Handler) (string, ResolverOptions, func()) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
options := ResolverOptions{
|
options := ResolverOptions{
|
||||||
Hosts: ConfigureDefaultRegistries(WithClient(client)),
|
Hosts: ConfigureDefaultRegistries(WithClient(client)),
|
||||||
// Set deprecated field for tests to use for configuration
|
// Set deprecated field for tests to use for configuration
|
||||||
|
Loading…
Reference in New Issue
Block a user