Merge pull request #1454 from grouville/registry-parsing

Re-implement docker registry parsing
This commit is contained in:
Andrea Luzzardi 2022-01-19 18:16:34 -08:00 committed by GitHub
commit 587f92c924
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 330 additions and 7 deletions

View File

@ -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) {

281
solver/registryauth_test.go Normal file
View File

@ -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)
}
}
}