diff --git a/solver/registryauth.go b/solver/registryauth.go index cf2684fa..04607758 100644 --- a/solver/registryauth.go +++ b/solver/registryauth.go @@ -2,16 +2,18 @@ package solver import ( "context" + "fmt" "strings" "sync" - "github.com/docker/distribution/reference" bkauth "github.com/moby/buildkit/session/auth" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +const defaultDockerDomain = "docker.io" + // RegistryAuthProvider is a buildkit provider for registry authentication // Adapted from: https://github.com/moby/buildkit/blob/master/session/auth/authprovider/authprovider.go type RegistryAuthProvider struct { @@ -42,7 +44,7 @@ func (a *RegistryAuthProvider) Register(server *grpc.Server) { func (a *RegistryAuthProvider) Credentials(ctx context.Context, req *bkauth.CredentialsRequest) (*bkauth.CredentialsResponse, error) { host := req.Host if host == "registry-1.docker.io" { - host = "docker.io" + host = defaultDockerDomain } a.m.RLock() @@ -53,7 +55,6 @@ func (a *RegistryAuthProvider) Credentials(ctx context.Context, req *bkauth.Cred if err != nil { return nil, err } - if u == host { return auth, nil } @@ -62,16 +63,57 @@ func (a *RegistryAuthProvider) Credentials(ctx context.Context, req *bkauth.Cred return &bkauth.CredentialsResponse{}, nil } +// Parsing function based on splitReposSearchTerm +// "github.com/docker/docker/registry" func parseAuthHost(host string) (string, error) { host = strings.TrimPrefix(host, "http://") host = strings.TrimPrefix(host, "https://") + host = strings.TrimSuffix(host, "/") - ref, err := reference.ParseNormalizedNamed(host) + // Remove everything after @ + nameParts := strings.SplitN(host, "@", 2) + host = nameParts[0] - if err != nil { - return "", err + // if ":" > 1, trim after last ":" found + if strings.Count(host, ":") > 1 { + host = host[:strings.LastIndex(host, ":")] } - return reference.Domain(ref), nil + + // if ":" > 0, trim after last ":" found if it contains "." + // ex: samalba/hipache:1.15, registry.com:5000:1.0 + if strings.Count(host, ":") > 0 { + tmpStr := host[strings.LastIndex(host, ":"):] + if strings.Count(tmpStr, ".") > 0 { + host = host[:strings.LastIndex(host, ":")] + } + } + + nameParts = strings.SplitN(host, "/", 2) + var domain string + switch { + // Localhost registry parsing + case strings.Contains(nameParts[0], "localhost"): + domain = nameParts[0] + // If the split returned an array of len 1 that doesn't contain any . + // ex: ubuntu + case len(nameParts) == 1 && !strings.Contains(nameParts[0], "."): + domain = defaultDockerDomain + // if the split does not contain "." nor ":", but contains images + // ex: samalba/hipache, samalba/hipache:1.15, samalba/hipache@sha:... + case !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":"): + domain = defaultDockerDomain + case nameParts[0] == "registry-1.docker.io": + domain = defaultDockerDomain + case nameParts[0] == "index.docker.io": + domain = defaultDockerDomain + // Private remaining registry parsing + case strings.Contains(nameParts[0], "."): + domain = nameParts[0] + // Fail by default + default: + return "", fmt.Errorf("failed parsing [%s] expected host format: [%s]", nameParts[0], "registrydomain.extension") + } + return domain, nil } func (a *RegistryAuthProvider) FetchToken(ctx context.Context, req *bkauth.FetchTokenRequest) (rr *bkauth.FetchTokenResponse, err error) { diff --git a/solver/registryauth_test.go b/solver/registryauth_test.go new file mode 100644 index 00000000..33e30a37 --- /dev/null +++ b/solver/registryauth_test.go @@ -0,0 +1,281 @@ +package solver + +import ( + "testing" +) + +func TestParseAuthHost(t *testing.T) { + type hcase struct { + Host, Domain string + } + + scases := []hcase{ + // Short + { + Host: "foo", + Domain: "docker.io", + }, + { + Host: "foo:1.1", + Domain: "docker.io", + }, + { + Host: "foo@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // Short image + { + Host: "foo/bar", + Domain: "docker.io", + }, + { + Host: "foo/bar:1.1", + Domain: "docker.io", + }, + { + Host: "foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // Private registry + { + Host: "registry.com", + Domain: "registry.com", + }, + { + Host: "registry.com:1.1", + Domain: "registry.com", + }, + { + Host: "registry.com@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "registry.com", + }, + + // Private image + { + Host: "registry.com/foo/bar", + Domain: "registry.com", + }, + { + Host: "registry.com/foo/bar:1.1", + Domain: "registry.com", + }, + { + Host: "registry.com/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "registry.com", + }, + + // Private registry with port + { + Host: "registry.com:5000", + Domain: "registry.com:5000", + }, + { + Host: "registry.com:5000:1.1", + Domain: "registry.com:5000", + }, + { + Host: "registry.com:5000@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "registry.com:5000", + }, + + // Private image with port + { + Host: "registry.com:5000/foo/bar", + Domain: "registry.com:5000", + }, + { + Host: "registry.com:5000/foo/bar:1.1", + Domain: "registry.com:5000", + }, + { + Host: "registry.com:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "registry.com:5000", + }, + + // docker.io short + { + Host: "docker.io", + Domain: "docker.io", + }, + { + Host: "docker.io:1.1", + Domain: "docker.io", + }, + { + Host: "docker.io@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // docker.io image + { + Host: "docker.io/foo/bar", + Domain: "docker.io", + }, + { + Host: "docker.io/foo/bar:1.1", + Domain: "docker.io", + }, + { + Host: "docker.io/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // registry-1.docker.io short + { + Host: "registry-1.docker.io", + Domain: "docker.io", + }, + { + Host: "registry-1.docker.io:1.1", + Domain: "docker.io", + }, + { + Host: "registry-1.docker.io@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // registry-1.docker.io image + { + Host: "registry-1.docker.io/foo/bar", + Domain: "docker.io", + }, + { + Host: "registry-1.docker.io/foo/bar:1.1", + Domain: "docker.io", + }, + { + Host: "registry-1.docker.io/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // index.docker.io short + { + Host: "index.docker.io", + Domain: "docker.io", + }, + { + Host: "index.docker.io:1.1", + Domain: "docker.io", + }, + { + Host: "index.docker.io@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // index.docker.io image + { + Host: "index.docker.io/foo/bar", + Domain: "docker.io", + }, + { + Host: "index.docker.io/foo/bar:1.1", + Domain: "docker.io", + }, + { + Host: "index.docker.io/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "docker.io", + }, + + // localhost repository + { + Host: "localhost", + Domain: "localhost", + }, + { + Host: "localhost:1.1", + Domain: "localhost", + }, + { + Host: "localhost@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "localhost", + }, + + // localhost image + { + Host: "localhost/foo/bar", + Domain: "localhost", + }, + { + Host: "localhost/foo/bar:1.1", + Domain: "localhost", + }, + { + Host: "localhost/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "localhost", + }, + + // localhost repository with port + { + Host: "localhost:5000", + Domain: "localhost:5000", + }, + { + Host: "localhost:5000:1.1", + Domain: "localhost:5000", + }, + { + Host: "localhost:5000@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "localhost:5000", + }, + + // localhost image with port + { + Host: "localhost:5000/foo/bar", + Domain: "localhost:5000", + }, + { + Host: "localhost:5000/foo/bar:1.1", + Domain: "localhost:5000", + }, + { + Host: "localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb", + Domain: "localhost:5000", + }, + + // empty host + { + Host: "", + Domain: "docker.io", + }, + { + Host: "/jo", + Domain: "docker.io", + }, + } + + fcases := []hcase{ + { + Host: ":/jo", + }, + } + + type output struct { + expected, actual string + } + + successRefs := []output{} + for _, scase := range scases { + named, err := parseAuthHost(scase.Host) + if err != nil { + t.Fatalf("Invalid normalized reference for [%q]. Got %q", scase, err) + } + successRefs = append(successRefs, output{ + actual: named, + expected: scase.Domain, + }) + } + for _, r := range successRefs { + if r.expected != r.actual { + t.Fatalf("Invalid normalized reference for [%q]. Expected %q, got %q", r, r.expected, r.actual) + } + } + + for _, fcase := range fcases { + named, err := parseAuthHost(fcase.Host) + if err == nil { + t.Fatalf("Invalid normalized reference for [%q]. Expected failure for %q", fcase, named) + } + } +}