diff --git a/bake/bake.go b/bake/bake.go index 42c025f34..b42c52dce 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -1330,16 +1330,6 @@ func updateContext(t *build.Inputs, inp *Input) { t.ContextPath = inp.URL } -func isRemoteContext(t build.Inputs, inp *Input) bool { - if urlutil.IsRemoteURL(t.ContextPath) { - return true - } - if inp != nil && urlutil.IsRemoteURL(inp.URL) && !strings.HasPrefix(t.ContextPath, "cwd://") { - return true - } - return false -} - func collectLocalPaths(t build.Inputs) []string { var out []string if t.ContextState == nil { @@ -1509,19 +1499,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 inp != nil && shouldAttachGitAuthSecrets(inp.URL, bi.ContextPath) { + secrets = append(secrets, gitAuthSecretsFromEnv(inp.URL)...) } bo.SecretSpecs = secrets.Normalize() secretAttachment, err := build.CreateSecrets(bo.SecretSpecs) diff --git a/bake/gitauth.go b/bake/gitauth.go new file mode 100644 index 000000000..71a07ab65 --- /dev/null +++ b/bake/gitauth.go @@ -0,0 +1,78 @@ +package bake + +import ( + "os" + "strings" + + "github.com/docker/buildx/util/buildflags" + "github.com/docker/buildx/util/urlutil" + "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(remoteURL string) buildflags.Secrets { + return gitAuthSecretsFromEnviron(os.Environ(), remoteURL) +} + +func gitAuthSecretsFromEnviron(environ []string, remoteURL string) buildflags.Secrets { + host, ok := gitAuthHostFromURL(remoteURL) + if !ok { + return nil + } + secrets := make(buildflags.Secrets, 0, 2) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthTokenKey, bakeGitAuthTokenEnv, environ, host)...) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthHeaderKey, bakeGitAuthHeaderEnv, environ, host)...) + return secrets +} + +func gitAuthSecretsForEnv(secretIDPrefix, envPrefix string, environ []string, host string) buildflags.Secrets { + envKey, ok := findGitAuthEnvKey(envPrefix, environ) + if !ok || host == "" { + return nil + } + return buildflags.Secrets{&buildflags.Secret{ + ID: secretIDPrefix + "." + host, + Env: envKey, + }} +} + +func shouldAttachGitAuthSecrets(inputURL, contextPath string) bool { + if !urlutil.IsRemoteURL(inputURL) || !urlutil.IsRemoteURL(contextPath) { + return false + } + inputHost, ok := gitAuthHostFromURL(inputURL) + if !ok { + return false + } + contextHost, ok := gitAuthHostFromURL(contextPath) + if !ok { + return false + } + return strings.EqualFold(inputHost, contextHost) +} + +func gitAuthHostFromURL(remoteURL string) (string, bool) { + gitURL, err := gitutil.ParseURL(remoteURL) + if err != nil || gitURL.Host == "" { + return "", false + } + return gitURL.Host, true +} + +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 000000000..6ead16bcf --- /dev/null +++ b/bake/gitauth_test.go @@ -0,0 +1,83 @@ +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("empty without remote url", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + "=token", + bakeGitAuthHeaderEnv + "=basic", + }, "") + require.Empty(t, 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 + ".example.com|" + bakeGitAuthTokenEnv, + 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", + }, "https://example.com/org/repo.git") + require.Empty(t, secrets) + }) +} + +func TestShouldAttachGitAuthSecrets(t *testing.T) { + tests := []struct { + name string + inputURL string + contextPath string + want bool + }{ + { + name: "same host http urls", + inputURL: "https://example.com/org/repo.git", + contextPath: "https://example.com/another/repo.git", + want: true, + }, + { + name: "same host mixed git url styles", + inputURL: "https://example.com/org/repo.git", + contextPath: "git@example.com:another/repo.git", + want: true, + }, + { + name: "different hosts", + inputURL: "https://example.com/org/repo.git", + contextPath: "https://other.example.com/org/repo.git", + want: false, + }, + { + name: "non remote context", + inputURL: "https://example.com/org/repo.git", + contextPath: "cwd://src", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, shouldAttachGitAuthSecrets(tt.inputURL, tt.contextPath)) + }) + } +} + +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 94b1da494..2f4dfc41a 100644 --- a/bake/remote.go +++ b/bake/remote.go @@ -44,20 +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", - }) - } - if len(gitAuthSecrets) > 0 { + if gitAuthSecrets := gitAuthSecretsFromEnv(url); len(gitAuthSecrets) > 0 { if secrets, err := build.CreateSecrets(gitAuthSecrets); err == nil { sessions = append(sessions, secrets) }