106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
package updater
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/distribution/reference"
|
|
"github.com/docker/docker/api/types/registry"
|
|
)
|
|
|
|
// RegistryAuth holds inline credentials from a docker config.json, already
|
|
// encoded as X-Registry-Auth values. A nil *RegistryAuth means anonymous
|
|
// access.
|
|
type RegistryAuth struct {
|
|
byHost map[string]string
|
|
}
|
|
|
|
type dockerConfigFile struct {
|
|
Auths map[string]struct {
|
|
Auth string `json:"auth"`
|
|
IdentityToken string `json:"identitytoken"`
|
|
} `json:"auths"`
|
|
CredsStore string `json:"credsStore"`
|
|
CredHelpers map[string]string `json:"credHelpers"`
|
|
}
|
|
|
|
// LoadRegistryAuth reads dockerConfigDir/config.json. An empty dir returns
|
|
// nil, nil. Only inline `auth` and `identitytoken` entries are supported;
|
|
// credential helpers are not available inside the agent container, which is
|
|
// logged once.
|
|
func LoadRegistryAuth(dockerConfigDir string, log *slog.Logger) (*RegistryAuth, error) {
|
|
if dockerConfigDir == "" {
|
|
return nil, nil
|
|
}
|
|
path := filepath.Join(dockerConfigDir, "config.json")
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read %s: %w", path, err)
|
|
}
|
|
var cfg dockerConfigFile
|
|
if err := json.Unmarshal(raw, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse %s: %w", path, err)
|
|
}
|
|
if (cfg.CredsStore != "" || len(cfg.CredHelpers) > 0) && log != nil {
|
|
log.Warn("registry_cred_helpers_unsupported", "path", path)
|
|
}
|
|
a := &RegistryAuth{byHost: make(map[string]string)}
|
|
for key, entry := range cfg.Auths {
|
|
host := normaliseRegistryKey(key)
|
|
ac := registry.AuthConfig{ServerAddress: host}
|
|
switch {
|
|
case entry.IdentityToken != "":
|
|
ac.IdentityToken = entry.IdentityToken
|
|
case entry.Auth != "":
|
|
dec, err := base64.StdEncoding.DecodeString(entry.Auth)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: auth for %s is not base64", path, key)
|
|
}
|
|
user, pass, ok := strings.Cut(string(dec), ":")
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s: auth for %s is not user:password", path, key)
|
|
}
|
|
ac.Username, ac.Password = user, pass
|
|
default:
|
|
continue
|
|
}
|
|
enc, err := registry.EncodeAuthConfig(ac)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode auth for %s: %w", key, err)
|
|
}
|
|
a.byHost[host] = enc
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// normaliseRegistryKey reduces a config.json key to a bare registry host,
|
|
// mapping every Docker Hub spelling to "docker.io".
|
|
func normaliseRegistryKey(key string) string {
|
|
k := strings.TrimPrefix(strings.TrimPrefix(key, "https://"), "http://")
|
|
if i := strings.Index(k, "/"); i >= 0 {
|
|
k = k[:i]
|
|
}
|
|
switch k {
|
|
case "index.docker.io", "docker.io", "registry-1.docker.io":
|
|
return "docker.io"
|
|
}
|
|
return k
|
|
}
|
|
|
|
// For returns the X-Registry-Auth value for repo's registry, or "".
|
|
func (a *RegistryAuth) For(repo string) string {
|
|
if a == nil {
|
|
return ""
|
|
}
|
|
named, err := reference.ParseNormalizedNamed(repo)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return a.byHost[reference.Domain(named)]
|
|
}
|