registry auth fallback bugfix (backport #672) (#684)

Signed-off-by: Adam Martin <adam.martin@ranchergovernment.com>
Co-authored-by: Adam Martin <adam.martin@ranchergovernment.com>
This commit is contained in:
mergify[bot]
2026-07-21 13:16:21 -04:00
committed by GitHub
co-authored by Adam Martin
parent 43e361d022
commit 13512a3652
3 changed files with 66 additions and 5 deletions
+5 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"hauler.dev/go/hauler/v2/internal/flags"
"hauler.dev/go/hauler/v2/pkg/consts"
"hauler.dev/go/hauler/v2/pkg/content"
"hauler.dev/go/hauler/v2/pkg/log"
)
@@ -21,8 +22,10 @@ func New(ctx context.Context, ro *flags.CliRootOpts) *cobra.Command {
l.SetLevel(ro.LogLevel)
l.Debugf("running cli command [%s]", cmd.CommandPath())
// Suppress WARN-level messages from containerd and other
// libraries that use the global logrus logger.
if dir, set := content.SetDefaultDockerConfig(); set {
l.Debugf("defaulted $DOCKER_CONFIG to [%s] for registry credential resolution", dir)
}
if ro.LogLevel == "debug" {
logrus.SetLevel(logrus.DebugLevel)
} else {
+54
View File
@@ -0,0 +1,54 @@
package content
import (
"os"
"os/user"
"path/filepath"
)
// currentUser is a seam so tests can simulate a running UID with no passwd
// entry (where user.Current() returns an error).
var currentUser = user.Current
// SetDefaultDockerConfig defaults the DOCKER_CONFIG environment variable to the
// directory where `hauler login` (crane -> docker/cli) writes credentials, so
// that go-containerregistry's authn.DefaultKeychain -- used to resolve registry
// credentials during `hauler store copy registry://`, `add`, and `sync` -- can
// find them even when $HOME is unset.
//
// It replicates docker/cli's config.Dir()/getHomeDir() resolution using only the
// standard library (no docker/cli dependency): DOCKER_CONFIG if already set,
// otherwise <home>/.docker, where home is os.UserHomeDir() ($HOME on Unix,
// %USERPROFILE% on Windows) with an /etc/passwd fallback via os/user.Current()
// when $HOME is empty -- exactly the passwd fallback `hauler login` uses.
//
// When no home directory can be resolved at all ($HOME empty AND
// user.Current() fails or returns an empty HomeDir), this mirrors docker/cli's
// own config.Dir(), which computes filepath.Join(getHomeDir(), ".docker"): with
// getHomeDir() == "", that join collapses to the relative path ".docker". So
// DOCKER_CONFIG is defaulted to the relative path ".docker" in that case too,
// keeping `hauler login`'s (relative) write and the keychain's (relative) read
// in agreement as long as both run from the same working directory.
//
// The only remaining no-op case is when DOCKER_CONFIG is already explicitly
// set (an explicit value always wins). Setting DOCKER_CONFIG does not disable
// DefaultKeychain's $REGISTRY_AUTH_FILE / Podman auth.json fallbacks: those
// still apply when no config.json exists at the set path.
//
// Returns the directory it set and true, or "" and false if it made no change.
func SetDefaultDockerConfig() (string, bool) {
if os.Getenv("DOCKER_CONFIG") != "" {
return "", false
}
home, err := os.UserHomeDir()
if err != nil || home == "" {
if u, uerr := currentUser(); uerr == nil {
home = u.HomeDir
}
}
dir := filepath.Join(home, ".docker")
os.Setenv("DOCKER_CONFIG", dir)
return dir, true
}
+7 -3
View File
@@ -100,15 +100,19 @@ func NewRegistryTarget(host string, opts RegistryOptions, client *http.Client) *
// Bridge to go-containerregistry's keychain for credential lookup.
reg, err := goname.NewRegistry(h, goname.Insecure)
if err != nil {
return "", "", nil
return "", "", fmt.Errorf("parsing registry host [%s] for credential lookup: %w", h, err)
}
a, err := goauthn.DefaultKeychain.Resolve(reg)
if err != nil || a == goauthn.Anonymous {
if err != nil {
// don't fall back to anonymous on a real resolution error
return "", "", fmt.Errorf("resolving credentials for [%s]: %w", h, err)
}
if a == goauthn.Anonymous {
return "", "", nil
}
cfg, err := a.Authorization()
if err != nil {
return "", "", nil
return "", "", fmt.Errorf("reading resolved authorization for [%s]: %w", h, err)
}
return cfg.Username, cfg.Password, nil
}),