This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
dagger/solver/registryauth.go
guillaume da7b77ed5c Re-implement docker registry parsing
Dagger used to rely on registry.ParseNormalize function to extract registry domains from images / registry URLs.
However, it contained some flaws for private registries.
This PR fixes that by implementing a test suite around it, and tweaks the splitReposSearchTerm function from the docker CLI.

The logic of splitReposSearchTerm is kept, and enhanced to fit to all of our use cases.
In case of a bad matching, a clear error is returned

Signed-off-by: guillaume <guillaume.derouville@gmail.com>
2022-01-19 02:03:17 +01:00

130 lines
3.9 KiB
Go

package solver
import (
"context"
"fmt"
"strings"
"sync"
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 {
credentials map[string]*bkauth.CredentialsResponse
m sync.RWMutex
}
func NewRegistryAuthProvider() *RegistryAuthProvider {
return &RegistryAuthProvider{
credentials: map[string]*bkauth.CredentialsResponse{},
}
}
func (a *RegistryAuthProvider) AddCredentials(target, username, secret string) {
a.m.Lock()
defer a.m.Unlock()
a.credentials[target] = &bkauth.CredentialsResponse{
Username: username,
Secret: secret,
}
}
func (a *RegistryAuthProvider) Register(server *grpc.Server) {
bkauth.RegisterAuthServer(server, a)
}
func (a *RegistryAuthProvider) Credentials(ctx context.Context, req *bkauth.CredentialsRequest) (*bkauth.CredentialsResponse, error) {
host := req.Host
if host == "registry-1.docker.io" {
host = defaultDockerDomain
}
a.m.RLock()
defer a.m.RUnlock()
for authHost, auth := range a.credentials {
u, err := parseAuthHost(authHost)
if err != nil {
return nil, err
}
if u == host {
return auth, nil
}
}
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, "/")
// Remove everything after @
nameParts := strings.SplitN(host, "@", 2)
host = nameParts[0]
// if ":" > 1, trim after last ":" found
if strings.Count(host, ":") > 1 {
host = host[:strings.LastIndex(host, ":")]
}
// 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) {
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
}
func (a *RegistryAuthProvider) GetTokenAuthority(ctx context.Context, req *bkauth.GetTokenAuthorityRequest) (*bkauth.GetTokenAuthorityResponse, error) {
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
}
func (a *RegistryAuthProvider) VerifyTokenAuthority(ctx context.Context, req *bkauth.VerifyTokenAuthorityRequest) (*bkauth.VerifyTokenAuthorityResponse, error) {
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
}