Skip to content

Commit 3953dc7

Browse files
authored
Merge pull request moby#5336 from tonistiigi/dockerfile-secret-env-mask
dockerfile: mask usage of secret env in command name
2 parents 734a6cc + f61dde1 commit 3953dc7

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
12631263
return err
12641264
}
12651265
env := getEnv(d.state)
1266-
opt = append(opt, llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(&shlex, customname, env)), d.prefixPlatform, pl, env)))
1266+
opt = append(opt, llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(&shlex, customname, withSecretEnvMask(c, env))), d.prefixPlatform, pl, env)))
12671267
for _, h := range dopt.extraHosts {
12681268
opt = append(opt, llb.AddExtraHost(h.Host, h.IP))
12691269
}

frontend/dockerfile/dockerfile2llb/convert_secrets.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/moby/buildkit/client/llb"
77
"github.com/moby/buildkit/frontend/dockerfile/instructions"
88
"github.com/moby/buildkit/frontend/dockerfile/parser"
9+
"github.com/moby/buildkit/frontend/dockerfile/shell"
910
"github.com/pkg/errors"
1011
)
1112

@@ -69,3 +70,52 @@ func dispatchSecret(d *dispatchState, m *instructions.Mount, loc []parser.Range)
6970

7071
return llb.AddSecretWithDest(id, target, opts...), nil
7172
}
73+
74+
// withSecretEnvMask returns an EnvGetter that masks secret values in the environment.
75+
// This is not needed to hide actual secret values but to make it clear that the value is loaded from a secret.
76+
func withSecretEnvMask(c *instructions.RunCommand, env shell.EnvGetter) shell.EnvGetter {
77+
ev := &llb.EnvList{}
78+
set := false
79+
mounts := instructions.GetMounts(c)
80+
for _, mount := range mounts {
81+
if mount.Type == instructions.MountTypeSecret {
82+
if mount.Env != nil {
83+
ev = ev.AddOrReplace(*mount.Env, "****")
84+
set = true
85+
}
86+
}
87+
}
88+
if !set {
89+
return env
90+
}
91+
return &secretEnv{
92+
base: env,
93+
env: ev,
94+
}
95+
}
96+
97+
type secretEnv struct {
98+
base shell.EnvGetter
99+
env *llb.EnvList
100+
}
101+
102+
func (s *secretEnv) Get(key string) (string, bool) {
103+
v, ok := s.env.Get(key)
104+
if ok {
105+
return v, true
106+
}
107+
return s.base.Get(key)
108+
}
109+
110+
func (s *secretEnv) Keys() []string {
111+
bkeys := s.base.Keys()
112+
skeys := s.env.Keys()
113+
keys := make([]string, 0, len(bkeys)+len(skeys))
114+
for _, k := range bkeys {
115+
if _, ok := s.env.Get(k); !ok {
116+
keys = append(keys, k)
117+
}
118+
}
119+
keys = append(keys, skeys...)
120+
return keys
121+
}

frontend/dockerfile/dockerfile_secrets_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package dockerfile
22

33
import (
4+
"strings"
45
"testing"
6+
"time"
57

68
"github.com/containerd/continuity/fs/fstest"
79
"github.com/moby/buildkit/client"
810
"github.com/moby/buildkit/frontend/dockerui"
911
"github.com/moby/buildkit/session"
1012
"github.com/moby/buildkit/session/secrets/secretsprovider"
1113
"github.com/moby/buildkit/util/testutil/integration"
14+
"github.com/stretchr/testify/assert"
1215
"github.com/stretchr/testify/require"
1316
"github.com/tonistiigi/fsutil"
1417
)
@@ -89,6 +92,7 @@ func testSecretAsEnviron(t *testing.T, sb integration.Sandbox) {
8992

9093
dockerfile := []byte(`
9194
FROM busybox
95+
ENV SECRET_ENV=foo
9296
RUN --mount=type=secret,id=mysecret,env=SECRET_ENV [ "$SECRET_ENV" == "pw" ] && [ ! -f /run/secrets/mysecret ] || false
9397
`)
9498

@@ -101,6 +105,22 @@ RUN --mount=type=secret,id=mysecret,env=SECRET_ENV [ "$SECRET_ENV" == "pw" ] &&
101105
require.NoError(t, err)
102106
defer c.Close()
103107

108+
done := make(chan struct{})
109+
status := make(chan *client.SolveStatus)
110+
hasStatus := false
111+
112+
go func() {
113+
for st := range status {
114+
for _, v := range st.Vertexes {
115+
if strings.Contains(v.Name, "/run/secrets/mysecret") {
116+
hasStatus = true
117+
assert.Contains(t, v.Name, `[ "****" == "pw" ] && `)
118+
}
119+
}
120+
}
121+
close(done)
122+
}()
123+
104124
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
105125
LocalMounts: map[string]fsutil.FS{
106126
dockerui.DefaultLocalNameDockerfile: dir,
@@ -109,8 +129,16 @@ RUN --mount=type=secret,id=mysecret,env=SECRET_ENV [ "$SECRET_ENV" == "pw" ] &&
109129
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
110130
"mysecret": []byte("pw"),
111131
})},
112-
}, nil)
132+
}, status)
113133
require.NoError(t, err)
134+
135+
select {
136+
case <-done:
137+
case <-time.After(10 * time.Second):
138+
require.Fail(t, "timed out waiting for status")
139+
}
140+
141+
require.True(t, hasStatus)
114142
}
115143

116144
func testSecretAsEnvironWithFileMount(t *testing.T, sb integration.Sandbox) {

0 commit comments

Comments
 (0)