Skip to content

Commit 13512a3

Browse files
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>
1 parent 43e361d commit 13512a3

3 files changed

Lines changed: 66 additions & 5 deletions

File tree

cmd/hauler/cli/cli.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/spf13/cobra"
99
"hauler.dev/go/hauler/v2/internal/flags"
1010
"hauler.dev/go/hauler/v2/pkg/consts"
11+
"hauler.dev/go/hauler/v2/pkg/content"
1112
"hauler.dev/go/hauler/v2/pkg/log"
1213
)
1314

@@ -21,8 +22,10 @@ func New(ctx context.Context, ro *flags.CliRootOpts) *cobra.Command {
2122
l.SetLevel(ro.LogLevel)
2223
l.Debugf("running cli command [%s]", cmd.CommandPath())
2324

24-
// Suppress WARN-level messages from containerd and other
25-
// libraries that use the global logrus logger.
25+
if dir, set := content.SetDefaultDockerConfig(); set {
26+
l.Debugf("defaulted $DOCKER_CONFIG to [%s] for registry credential resolution", dir)
27+
}
28+
2629
if ro.LogLevel == "debug" {
2730
logrus.SetLevel(logrus.DebugLevel)
2831
} else {

pkg/content/dockerconfig.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package content
2+
3+
import (
4+
"os"
5+
"os/user"
6+
"path/filepath"
7+
)
8+
9+
// currentUser is a seam so tests can simulate a running UID with no passwd
10+
// entry (where user.Current() returns an error).
11+
var currentUser = user.Current
12+
13+
// SetDefaultDockerConfig defaults the DOCKER_CONFIG environment variable to the
14+
// directory where `hauler login` (crane -> docker/cli) writes credentials, so
15+
// that go-containerregistry's authn.DefaultKeychain -- used to resolve registry
16+
// credentials during `hauler store copy registry://`, `add`, and `sync` -- can
17+
// find them even when $HOME is unset.
18+
//
19+
// It replicates docker/cli's config.Dir()/getHomeDir() resolution using only the
20+
// standard library (no docker/cli dependency): DOCKER_CONFIG if already set,
21+
// otherwise <home>/.docker, where home is os.UserHomeDir() ($HOME on Unix,
22+
// %USERPROFILE% on Windows) with an /etc/passwd fallback via os/user.Current()
23+
// when $HOME is empty -- exactly the passwd fallback `hauler login` uses.
24+
//
25+
// When no home directory can be resolved at all ($HOME empty AND
26+
// user.Current() fails or returns an empty HomeDir), this mirrors docker/cli's
27+
// own config.Dir(), which computes filepath.Join(getHomeDir(), ".docker"): with
28+
// getHomeDir() == "", that join collapses to the relative path ".docker". So
29+
// DOCKER_CONFIG is defaulted to the relative path ".docker" in that case too,
30+
// keeping `hauler login`'s (relative) write and the keychain's (relative) read
31+
// in agreement as long as both run from the same working directory.
32+
//
33+
// The only remaining no-op case is when DOCKER_CONFIG is already explicitly
34+
// set (an explicit value always wins). Setting DOCKER_CONFIG does not disable
35+
// DefaultKeychain's $REGISTRY_AUTH_FILE / Podman auth.json fallbacks: those
36+
// still apply when no config.json exists at the set path.
37+
//
38+
// Returns the directory it set and true, or "" and false if it made no change.
39+
func SetDefaultDockerConfig() (string, bool) {
40+
if os.Getenv("DOCKER_CONFIG") != "" {
41+
return "", false
42+
}
43+
44+
home, err := os.UserHomeDir()
45+
if err != nil || home == "" {
46+
if u, uerr := currentUser(); uerr == nil {
47+
home = u.HomeDir
48+
}
49+
}
50+
51+
dir := filepath.Join(home, ".docker")
52+
os.Setenv("DOCKER_CONFIG", dir)
53+
return dir, true
54+
}

pkg/content/registry.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,19 @@ func NewRegistryTarget(host string, opts RegistryOptions, client *http.Client) *
100100
// Bridge to go-containerregistry's keychain for credential lookup.
101101
reg, err := goname.NewRegistry(h, goname.Insecure)
102102
if err != nil {
103-
return "", "", nil
103+
return "", "", fmt.Errorf("parsing registry host [%s] for credential lookup: %w", h, err)
104104
}
105105
a, err := goauthn.DefaultKeychain.Resolve(reg)
106-
if err != nil || a == goauthn.Anonymous {
106+
if err != nil {
107+
// don't fall back to anonymous on a real resolution error
108+
return "", "", fmt.Errorf("resolving credentials for [%s]: %w", h, err)
109+
}
110+
if a == goauthn.Anonymous {
107111
return "", "", nil
108112
}
109113
cfg, err := a.Authorization()
110114
if err != nil {
111-
return "", "", nil
115+
return "", "", fmt.Errorf("reading resolved authorization for [%s]: %w", h, err)
112116
}
113117
return cfg.Username, cfg.Password, nil
114118
}),

0 commit comments

Comments
 (0)