diff --git a/bake/bake.go b/bake/bake.go index 50ea3f175fa2..0083bce117c6 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -1508,18 +1508,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { 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", - }) - } + secrets = append(secrets, gitAuthSecretsFromEnv()...) } 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 000000000000..0aa81198bbfb --- /dev/null +++ b/bake/gitauth.go @@ -0,0 +1,65 @@ +package bake + +import ( + "os" + "sort" + "strings" + + "github.com/docker/buildx/util/buildflags" + "github.com/moby/buildkit/client/llb" +) + +const ( + bakeGitAuthTokenEnv = "BUILDX_BAKE_GIT_AUTH_TOKEN" // #nosec G101 -- environment variable key, not a credential + bakeGitAuthHeaderEnv = "BUILDX_BAKE_GIT_AUTH_HEADER" +) + +func gitAuthSecretsFromEnv() buildflags.Secrets { + return gitAuthSecretsFromEnviron(os.Environ()) +} + +func gitAuthSecretsFromEnviron(environ []string) buildflags.Secrets { + secrets := make(buildflags.Secrets, 0, 2) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthTokenKey, bakeGitAuthTokenEnv, environ)...) + secrets = append(secrets, gitAuthSecretsForEnv(llb.GitAuthHeaderKey, bakeGitAuthHeaderEnv, environ)...) + return secrets +} + +func gitAuthSecretsForEnv(secretIDPrefix, envPrefix string, environ []string) buildflags.Secrets { + envKeys := findGitAuthEnvKeys(envPrefix, environ) + secrets := make(buildflags.Secrets, 0, len(envKeys)) + for _, envKey := range envKeys { + suffix := envKey[len(envPrefix):] + secrets = append(secrets, &buildflags.Secret{ + ID: secretIDPrefix + suffix, + Env: envKey, + }) + } + return secrets +} + +func findGitAuthEnvKeys(envPrefix string, environ []string) []string { + prefixUpper := strings.ToUpper(envPrefix) + var keys []string + for _, env := range environ { + key, _, ok := strings.Cut(env, "=") + if !ok { + continue + } + if !strings.HasPrefix(strings.ToUpper(key), prefixUpper) { + continue + } + if len(key) == len(envPrefix) { + keys = append(keys, key) + continue + } + if len(key) <= len(envPrefix)+1 { + continue + } + if key[len(envPrefix)] == '.' { + keys = append(keys, key) + } + } + sort.Strings(keys) + return keys +} diff --git a/bake/gitauth_test.go b/bake/gitauth_test.go new file mode 100644 index 000000000000..8730d442ac5e --- /dev/null +++ b/bake/gitauth_test.go @@ -0,0 +1,53 @@ +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("domain suffix keys", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + ".github.com=token", + bakeGitAuthHeaderEnv + ".github.com=bearer", + bakeGitAuthTokenEnv + ".example.com=token2", + bakeGitAuthTokenEnv + "=fallback", + }) + require.Equal(t, []string{ + llb.GitAuthTokenKey + "|" + bakeGitAuthTokenEnv, + llb.GitAuthTokenKey + ".example.com|" + bakeGitAuthTokenEnv + ".example.com", + llb.GitAuthTokenKey + ".github.com|" + bakeGitAuthTokenEnv + ".github.com", + llb.GitAuthHeaderKey + ".github.com|" + bakeGitAuthHeaderEnv + ".github.com", + }, secretPairs(secrets)) + }) + t.Run("ignores non-domain suffix", func(t *testing.T) { + secrets := gitAuthSecretsFromEnviron([]string{ + bakeGitAuthTokenEnv + "_EXTRA=token", + bakeGitAuthHeaderEnv + "-extra=basic", + bakeGitAuthTokenEnv + ".=bad", + bakeGitAuthHeaderEnv + ".=bad", + }) + 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..d57fc70168de 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() if len(gitAuthSecrets) > 0 { if secrets, err := build.CreateSecrets(gitAuthSecrets); err == nil { sessions = append(sessions, secrets) diff --git a/tests/bake.go b/tests/bake.go index 3139577757ee..b1ee6de95118 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -8,6 +8,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "net/url" "os" "path/filepath" "strconv" @@ -45,6 +46,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakeLocalMulti, testBakeRemote, testBakeRemoteAuth, + testBakeRemoteAuthHostSuffix, testBakeRemoteCmdContext, testBakeRemoteLocalOverride, testBakeLocalCwdOverride, @@ -662,6 +664,45 @@ EOT require.FileExists(t, filepath.Join(dirDest, "foo")) } +func testBakeRemoteAuthHostSuffix(t *testing.T, sb integration.Sandbox) { + bakefile := []byte(` +target "default" { + dockerfile-inline = <