Skip to content

Commit 53d77f7

Browse files
authored
Merge pull request moby#5215 from a-palchikov/dockerfile/secretasenv
frontend: allow mounting secret environment variables
2 parents 24421f1 + 96f746b commit 53d77f7

File tree

4 files changed

+129
-18
lines changed

4 files changed

+129
-18
lines changed

client/llb/exec.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net"
88
"sort"
9+
"strings"
910

1011
"github.com/moby/buildkit/solver/pb"
1112
"github.com/moby/buildkit/util/system"
@@ -290,7 +291,7 @@ func (e *ExecOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []
290291
if len(e.secrets) > 0 {
291292
addCap(&e.constraints, pb.CapExecMountSecret)
292293
for _, s := range e.secrets {
293-
if s.IsEnv {
294+
if s.Env != nil {
294295
addCap(&e.constraints, pb.CapExecSecretEnv)
295296
break
296297
}
@@ -388,16 +389,17 @@ func (e *ExecOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []
388389
}
389390

390391
for _, s := range e.secrets {
391-
if s.IsEnv {
392+
if s.Env != nil {
392393
peo.Secretenv = append(peo.Secretenv, &pb.SecretEnv{
393394
ID: s.ID,
394-
Name: s.Target,
395+
Name: *s.Env,
395396
Optional: s.Optional,
396397
})
397-
} else {
398+
}
399+
if s.Target != nil {
398400
pm := &pb.Mount{
399401
Input: pb.Empty,
400-
Dest: s.Target,
402+
Dest: *s.Target,
401403
MountType: pb.MountType_SECRET,
402404
SecretOpt: &pb.SecretOpt{
403405
ID: s.ID,
@@ -680,7 +682,19 @@ type SSHInfo struct {
680682
// AddSecret is a RunOption that adds a secret to the exec.
681683
func AddSecret(dest string, opts ...SecretOption) RunOption {
682684
return runOptionFunc(func(ei *ExecInfo) {
683-
s := &SecretInfo{ID: dest, Target: dest, Mode: 0400}
685+
s := &SecretInfo{ID: dest, Target: &dest, Mode: 0400}
686+
for _, opt := range opts {
687+
opt.SetSecretOption(s)
688+
}
689+
ei.Secrets = append(ei.Secrets, *s)
690+
})
691+
}
692+
693+
// AddSecretWithDest is a RunOption that adds a secret to the exec
694+
// with an optional destination.
695+
func AddSecretWithDest(src string, dest *string, opts ...SecretOption) RunOption {
696+
return runOptionFunc(func(ei *ExecInfo) {
697+
s := &SecretInfo{ID: src, Target: dest, Mode: 0400}
684698
for _, opt := range opts {
685699
opt.SetSecretOption(s)
686700
}
@@ -699,13 +713,15 @@ func (fn secretOptionFunc) SetSecretOption(si *SecretInfo) {
699713
}
700714

701715
type SecretInfo struct {
702-
ID string
703-
Target string
716+
ID string
717+
// Target optionally specifies the target for the secret mount
718+
Target *string
719+
// Env optionally names the environment variable for the secret
720+
Env *string
704721
Mode int
705722
UID int
706723
GID int
707724
Optional bool
708-
IsEnv bool
709725
}
710726

711727
var SecretOptional = secretOptionFunc(func(si *SecretInfo) {
@@ -721,7 +737,24 @@ func SecretID(id string) SecretOption {
721737
// SecretAsEnv defines if the secret should be added as an environment variable
722738
func SecretAsEnv(v bool) SecretOption {
723739
return secretOptionFunc(func(si *SecretInfo) {
724-
si.IsEnv = v
740+
if !v {
741+
si.Env = nil
742+
return
743+
}
744+
if si.Target == nil {
745+
return
746+
}
747+
target := strings.Clone(*si.Target)
748+
si.Env = &target
749+
si.Target = nil
750+
})
751+
}
752+
753+
// SecretAsEnvName defines if the secret should be added as an environment variable
754+
// with the specified name
755+
func SecretAsEnvName(v string) SecretOption {
756+
return secretOptionFunc(func(si *SecretInfo) {
757+
si.Env = &v
725758
})
726759
}
727760

frontend/dockerfile/dockerfile2llb/convert_secrets.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,17 @@ func dispatchSecret(d *dispatchState, m *instructions.Mount, loc []parser.Range)
2222
id = path.Base(m.Target)
2323
}
2424

25-
target := m.Target
26-
if target == "" {
27-
target = "/run/secrets/" + path.Base(id)
25+
var target *string
26+
if m.Target != "" {
27+
target = &m.Target
28+
}
29+
30+
if m.Env == nil {
31+
dest := m.Target
32+
if dest == "" {
33+
dest = "/run/secrets/" + path.Base(id)
34+
}
35+
target = &dest
2836
}
2937

3038
if _, ok := d.outline.secrets[id]; !ok {
@@ -39,6 +47,9 @@ func dispatchSecret(d *dispatchState, m *instructions.Mount, loc []parser.Range)
3947
if !m.Required {
4048
opts = append(opts, llb.SecretOptional)
4149
}
50+
if m.Env != nil {
51+
opts = append(opts, llb.SecretAsEnvName(*m.Env))
52+
}
4253

4354
if m.UID != nil || m.GID != nil || m.Mode != nil {
4455
var uid, gid, mode int
@@ -56,5 +67,5 @@ func dispatchSecret(d *dispatchState, m *instructions.Mount, loc []parser.Range)
5667
opts = append(opts, llb.SecretFileOpt(uid, gid, mode))
5768
}
5869

59-
return llb.AddSecret(target, opts...), nil
70+
return llb.AddSecretWithDest(id, target, opts...), nil
6071
}

frontend/dockerfile/dockerfile_secrets_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
var secretsTests = integration.TestFuncs(
1717
testSecretFileParams,
1818
testSecretRequiredWithoutValue,
19+
testSecretAsEnviron,
20+
testSecretAsEnvironWithFileMount,
1921
)
2022

2123
func init() {
@@ -80,3 +82,63 @@ RUN --mount=type=secret,required,id=mysecret foo
8082
require.Error(t, err)
8183
require.Contains(t, err.Error(), "secret mysecret: not found")
8284
}
85+
86+
func testSecretAsEnviron(t *testing.T, sb integration.Sandbox) {
87+
integration.SkipOnPlatform(t, "windows")
88+
f := getFrontend(t, sb)
89+
90+
dockerfile := []byte(`
91+
FROM busybox
92+
RUN --mount=type=secret,id=mysecret,env=SECRET_ENV [ "$SECRET_ENV" == "pw" ] && [ ! -f /run/secrets/mysecret ] || false
93+
`)
94+
95+
dir := integration.Tmpdir(
96+
t,
97+
fstest.CreateFile("Dockerfile", dockerfile, 0600),
98+
)
99+
100+
c, err := client.New(sb.Context(), sb.Address())
101+
require.NoError(t, err)
102+
defer c.Close()
103+
104+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
105+
LocalMounts: map[string]fsutil.FS{
106+
dockerui.DefaultLocalNameDockerfile: dir,
107+
dockerui.DefaultLocalNameContext: dir,
108+
},
109+
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
110+
"mysecret": []byte("pw"),
111+
})},
112+
}, nil)
113+
require.NoError(t, err)
114+
}
115+
116+
func testSecretAsEnvironWithFileMount(t *testing.T, sb integration.Sandbox) {
117+
integration.SkipOnPlatform(t, "windows")
118+
f := getFrontend(t, sb)
119+
120+
dockerfile := []byte(`
121+
FROM busybox
122+
RUN --mount=type=secret,id=mysecret,target=/run/secrets/secret,env=SECRET_ENV [ "$SECRET_ENV" == "pw" ] && [ -f /run/secrets/secret ] || false
123+
`)
124+
125+
dir := integration.Tmpdir(
126+
t,
127+
fstest.CreateFile("Dockerfile", dockerfile, 0600),
128+
)
129+
130+
c, err := client.New(sb.Context(), sb.Address())
131+
require.NoError(t, err)
132+
defer c.Close()
133+
134+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
135+
LocalMounts: map[string]fsutil.FS{
136+
dockerui.DefaultLocalNameDockerfile: dir,
137+
dockerui.DefaultLocalNameContext: dir,
138+
},
139+
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
140+
"mysecret": []byte("pw"),
141+
})},
142+
}, nil)
143+
require.NoError(t, err)
144+
}

frontend/dockerfile/instructions/commands_runmount.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,12 @@ type Mount struct {
122122
CacheID string
123123
CacheSharing ShareMode
124124
Required bool
125-
Mode *uint64
126-
UID *uint64
127-
GID *uint64
125+
// Env optionally specifies the name of the environment variable for a secret.
126+
// A pointer to an empty value uses the default
127+
Env *string
128+
Mode *uint64
129+
UID *uint64
130+
GID *uint64
128131
}
129132

130133
func parseMount(val string, expander SingleWordExpander) (*Mount, error) {
@@ -252,9 +255,11 @@ func parseMount(val string, expander SingleWordExpander) (*Mount, error) {
252255
return nil, errors.Errorf("invalid value %s for gid", value)
253256
}
254257
m.GID = &gid
258+
case "env":
259+
m.Env = &value
255260
default:
256261
allKeys := []string{
257-
"type", "from", "source", "target", "readonly", "id", "sharing", "required", "size", "mode", "uid", "gid", "src", "dst", "destination", "ro", "rw", "readwrite",
262+
"type", "from", "source", "target", "readonly", "id", "sharing", "required", "size", "mode", "uid", "gid", "src", "dst", "destination", "ro", "rw", "readwrite", "env",
258263
}
259264
return nil, suggest.WrapError(errors.Errorf("unexpected key '%s' in '%s'", key, field), key, allKeys, true)
260265
}

0 commit comments

Comments
 (0)