From ac54d559198755712f1cac678e2394b655b23204 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:21:54 +0100 Subject: [PATCH] bake: derive git auth host from remote URL Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- bake/bake.go | 23 ++++--------- bake/bake_test.go | 26 ++++++++++++++ bake/gitauth.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ bake/gitauth_test.go | 49 +++++++++++++++++++++++++++ bake/remote.go | 14 +------- 5 files changed, 162 insertions(+), 30 deletions(-) create mode 100644 bake/gitauth.go create mode 100644 bake/gitauth_test.go diff --git a/bake/bake.go b/bake/bake.go index 42c025f3421a..28ecca65146c 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -1330,14 +1330,14 @@ func updateContext(t *build.Inputs, inp *Input) { t.ContextPath = inp.URL } -func isRemoteContext(t build.Inputs, inp *Input) bool { +func remoteContextURL(t build.Inputs, inp *Input) string { if urlutil.IsRemoteURL(t.ContextPath) { - return true + return t.ContextPath } if inp != nil && urlutil.IsRemoteURL(inp.URL) && !strings.HasPrefix(t.ContextPath, "cwd://") { - return true + return inp.URL } - return false + return "" } func collectLocalPaths(t build.Inputs) []string { @@ -1509,19 +1509,8 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { bo.Platforms = platforms secrets := t.Secrets - if isRemoteContext(bi, inp) { - if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok { - secrets = append(secrets, &buildflags.Secret{ - ID: llb.GitAuthTokenKey, - Env: "BUILDX_BAKE_GIT_AUTH_TOKEN", - }) - } - if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok { - secrets = append(secrets, &buildflags.Secret{ - ID: llb.GitAuthHeaderKey, - Env: "BUILDX_BAKE_GIT_AUTH_HEADER", - }) - } + if remoteURL := remoteContextURL(bi, inp); remoteURL != "" { + secrets = append(secrets, gitAuthSecretsFromEnv(remoteURL)...) } bo.SecretSpecs = secrets.Normalize() secretAttachment, err := build.CreateSecrets(bo.SecretSpecs) diff --git a/bake/bake_test.go b/bake/bake_test.go index 1a97a9455266..5bd968bcc393 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/buildx/build" "github.com/docker/buildx/util/buildflags" "github.com/moby/buildkit/util/entitlements" "github.com/stretchr/testify/assert" @@ -2415,6 +2416,31 @@ target "mtx" { } } +func TestRemoteContextURL(t *testing.T) { + t.Run("context path has priority when remote", func(t *testing.T) { + url := remoteContextURL(build.Inputs{ + ContextPath: "https://context.example.com/org/repo.git", + }, &Input{URL: "https://definition.example.com/org/repo.git"}) + require.Equal(t, "https://context.example.com/org/repo.git", url) + }) + t.Run("uses input url when context path is not remote", func(t *testing.T) { + url := remoteContextURL(build.Inputs{ + ContextPath: ".", + }, &Input{URL: "https://definition.example.com/org/repo.git"}) + require.Equal(t, "https://definition.example.com/org/repo.git", url) + }) + t.Run("returns empty when context path is cwd prefixed", func(t *testing.T) { + url := remoteContextURL(build.Inputs{ + ContextPath: "cwd://.", + }, &Input{URL: "https://definition.example.com/org/repo.git"}) + require.Empty(t, url) + }) + t.Run("returns empty without remote url", func(t *testing.T) { + require.Empty(t, remoteContextURL(build.Inputs{}, nil)) + require.Empty(t, remoteContextURL(build.Inputs{}, &Input{URL: "local-path"})) + }) +} + func stringify[V fmt.Stringer](values []V) []string { s := make([]string, len(values)) for i, v := range values { diff --git a/bake/gitauth.go b/bake/gitauth.go new file mode 100644 index 000000000000..b5e9891c8666 --- /dev/null +++ b/bake/gitauth.go @@ -0,0 +1,80 @@ +package bake + +import ( + "os" + "sort" + "strings" + + "github.com/docker/buildx/util/buildflags" + "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/util/gitutil" +) + +const ( + bakeGitAuthTokenEnv = "BUILDX_BAKE_GIT_AUTH_TOKEN" // #nosec G101 -- environment variable key, not a credential + bakeGitAuthHeaderEnv = "BUILDX_BAKE_GIT_AUTH_HEADER" +) + +func gitAuthSecretsFromEnv(remoteURLs ...string) buildflags.Secrets { + return gitAuthSecretsFromEnviron(os.Environ(), remoteURLs...) +} + +func gitAuthSecretsFromEnviron(environ []string, remoteURLs ...string) buildflags.Secrets { + hosts := gitAuthHostsFromURLs(remoteURLs) + secrets := make(buildflags.Secrets, 0, 2) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthTokenKey, bakeGitAuthTokenEnv, environ, hosts)...) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthHeaderKey, bakeGitAuthHeaderEnv, environ, hosts)...) + return secrets +} + +func gitAuthSecretsForEnv(secretIDPrefix, envPrefix string, environ []string, hosts []string) buildflags.Secrets { + envKey, ok := findGitAuthEnvKey(envPrefix, environ) + if !ok { + return nil + } + secrets := make(buildflags.Secrets, 0, len(hosts)+1) + secrets = append(secrets, &buildflags.Secret{ + ID: secretIDPrefix, + Env: envKey, + }) + for _, host := range hosts { + secrets = append(secrets, &buildflags.Secret{ + ID: secretIDPrefix + "." + host, + Env: envKey, + }) + } + return secrets +} + +func gitAuthHostsFromURLs(remoteURLs []string) []string { + if len(remoteURLs) == 0 { + return nil + } + hosts := make(map[string]struct{}, len(remoteURLs)) + for _, remoteURL := range remoteURLs { + gitURL, err := gitutil.ParseURL(remoteURL) + if err != nil || gitURL.Host == "" { + continue + } + hosts[gitURL.Host] = struct{}{} + } + out := make([]string, 0, len(hosts)) + for host := range hosts { + out = append(out, host) + } + sort.Strings(out) + return out +} + +func findGitAuthEnvKey(envKey string, environ []string) (string, bool) { + for _, env := range environ { + key, _, ok := strings.Cut(env, "=") + if !ok { + continue + } + if strings.EqualFold(key, envKey) { + return key, true + } + } + return "", false +} diff --git a/bake/gitauth_test.go b/bake/gitauth_test.go new file mode 100644 index 000000000000..108b4ea4c9a4 --- /dev/null +++ b/bake/gitauth_test.go @@ -0,0 +1,49 @@ +package bake + +import ( + "testing" + + "github.com/docker/buildx/util/buildflags" + "github.com/moby/buildkit/client/llb" + "github.com/stretchr/testify/require" +) + +func TestGitAuthSecretsFromEnviron(t *testing.T) { + t.Run("base keys", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + "=token", + bakeGitAuthHeaderEnv + "=basic", + }) + require.Equal(t, []string{ + llb.GitAuthTokenKey + "|" + bakeGitAuthTokenEnv, + llb.GitAuthHeaderKey + "|" + bakeGitAuthHeaderEnv, + }, secretPairs(secrets)) + }) + t.Run("derives host from remote url", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + "=token", + bakeGitAuthHeaderEnv + "=basic", + }, "https://example.com/org/repo.git") + require.Equal(t, []string{ + llb.GitAuthTokenKey + "|" + bakeGitAuthTokenEnv, + llb.GitAuthTokenKey + ".example.com|" + bakeGitAuthTokenEnv, + llb.GitAuthHeaderKey + "|" + bakeGitAuthHeaderEnv, + llb.GitAuthHeaderKey + ".example.com|" + bakeGitAuthHeaderEnv, + }, secretPairs(secrets)) + }) + t.Run("ignores host suffixed keys", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + ".example.com=token", + bakeGitAuthHeaderEnv + ".example.com=basic", + }) + require.Empty(t, secrets) + }) +} + +func secretPairs(secrets buildflags.Secrets) []string { + out := make([]string, 0, len(secrets)) + for _, s := range secrets { + out = append(out, s.ID+"|"+s.Env) + } + return out +} diff --git a/bake/remote.go b/bake/remote.go index 94b1da494878..eca15900e172 100644 --- a/bake/remote.go +++ b/bake/remote.go @@ -44,19 +44,7 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name }}); err == nil { sessions = append(sessions, ssh) } - var gitAuthSecrets []*buildflags.Secret - if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok { - gitAuthSecrets = append(gitAuthSecrets, &buildflags.Secret{ - ID: llb.GitAuthTokenKey, - Env: "BUILDX_BAKE_GIT_AUTH_TOKEN", - }) - } - if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok { - gitAuthSecrets = append(gitAuthSecrets, &buildflags.Secret{ - ID: llb.GitAuthHeaderKey, - Env: "BUILDX_BAKE_GIT_AUTH_HEADER", - }) - } + gitAuthSecrets := gitAuthSecretsFromEnv(url) if len(gitAuthSecrets) > 0 { if secrets, err := build.CreateSecrets(gitAuthSecrets); err == nil { sessions = append(sessions, secrets)