128 lines
4.0 KiB
Go
128 lines
4.0 KiB
Go
package registry
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
iofs "io/fs"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/containerd/containerd/remotes"
|
|
dockerremote "github.com/containerd/containerd/remotes/docker"
|
|
)
|
|
|
|
// given a registry hostname, return the "user:pass" string decoded from base64 in ~/.docker/config.json
|
|
func lookupDockerAuthCredentials(registry string) (usernameColonPassword string, err error) {
|
|
dockerConfigDir := os.Getenv("DOCKER_CONFIG")
|
|
if dockerConfigDir == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
dockerConfigDir = filepath.Join(home, ".docker")
|
|
}
|
|
dockerConfigFile := filepath.Join(dockerConfigDir, "config.json")
|
|
|
|
file, err := os.Open(dockerConfigFile)
|
|
if err != nil {
|
|
if errors.Is(err, iofs.ErrNotExist) {
|
|
err = nil
|
|
}
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
dockerConfig := struct {
|
|
Auths map[string]struct {
|
|
Auth string `json:"auth"`
|
|
} `json:"auths"`
|
|
}{}
|
|
err = json.NewDecoder(file).Decode(&dockerConfig)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
hosts := []string{registry}
|
|
switch registry {
|
|
case "docker.io", "index.docker.io", "":
|
|
hosts = []string{"docker.io", "index.docker.io"}
|
|
}
|
|
|
|
for _, host := range hosts {
|
|
// teeeeeeeeeeeechnically, this should loop over the keys in dockerConfig.Auths and strip the "https://" prefix from each and the "/*" suffix from each and then match on that, but what's the point of a map if you can't get a fast lookup?? (https://github.com/moby/moby/blob/34b56728ed7101c6b3cc0405f5fd6351073a8253/registry/auth.go#L202-L235)
|
|
for _, check := range []string{host, "https://" + host + "/v1/"} {
|
|
if authObj, ok := dockerConfig.Auths[check]; ok {
|
|
if base64val := authObj.Auth; base64val != "" {
|
|
rawVal, err := base64.StdEncoding.DecodeString(base64val)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(rawVal), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
var (
|
|
resolver remotes.Resolver
|
|
resolverOnce sync.Once
|
|
)
|
|
|
|
// returns a containerd "Resolver" suitable for interacting with registries (that will transparently honor DOCKERHUB_PUBLIC_PROXY for read-only lookups *and* deal with looking up credentials from ~/.docker/config.json)
|
|
func NewDockerAuthResolver() remotes.Resolver {
|
|
resolverOnce.Do(func() {
|
|
resolver = dockerremote.NewResolver(dockerremote.ResolverOptions{
|
|
Hosts: func(domain string) ([]dockerremote.RegistryHost, error) {
|
|
// https://github.com/containerd/containerd/blob/v1.6.10/remotes/docker/registry.go#L152-L198
|
|
config := dockerremote.RegistryHost{
|
|
Host: domain,
|
|
Scheme: "https",
|
|
Path: "/v2",
|
|
Capabilities: dockerremote.HostCapabilityPull | dockerremote.HostCapabilityResolve | dockerremote.HostCapabilityPush,
|
|
Authorizer: dockerremote.NewDockerAuthorizer(dockerremote.WithAuthCreds(func(_ string) (string, string, error) {
|
|
usernameColonPassword, err := lookupDockerAuthCredentials(domain)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
username, password, _ := strings.Cut(usernameColonPassword, ":")
|
|
return username, password, nil
|
|
})),
|
|
}
|
|
if domain == "docker.io" {
|
|
// https://github.com/containerd/containerd/blob/v1.6.10/remotes/docker/registry.go#L193
|
|
config.Host = "registry-1.docker.io"
|
|
|
|
if publicProxy := os.Getenv("DOCKERHUB_PUBLIC_PROXY"); publicProxy != "" {
|
|
publicProxyURL, err := url.Parse(publicProxy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
proxyConfig := dockerremote.RegistryHost{
|
|
Host: publicProxyURL.Host,
|
|
Scheme: publicProxyURL.Scheme,
|
|
Path: path.Join(publicProxyURL.Path, config.Path),
|
|
Capabilities: dockerremote.HostCapabilityPull | dockerremote.HostCapabilityResolve,
|
|
}
|
|
return []dockerremote.RegistryHost{
|
|
proxyConfig,
|
|
config,
|
|
}, nil
|
|
}
|
|
} else if strings.Contains(domain, "localhost") {
|
|
config.Scheme = "http"
|
|
}
|
|
return []dockerremote.RegistryHost{config}, nil
|
|
},
|
|
})
|
|
})
|
|
return resolver
|
|
}
|