Skip to content

Commit c15110e

Browse files
committed
bake: cache-from/cache-to options no longer print sensitive values
This refactors how the cache-from/cache-to composable attributes work so they no longer print sensitive values that are automatically added. This also expands the available syntax that works with the cache options. It is now possible to interleave the csv syntax with the object syntax without any problems. The canonical form is still the object syntax and variables are resolved according to that syntax. `cache-from` and `cache-to` now correctly ignore empty string inputs so these can be used with variables. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
1 parent d6d713a commit c15110e

File tree

10 files changed

+409
-103
lines changed

10 files changed

+409
-103
lines changed

bake/bake.go

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -698,30 +698,30 @@ type Target struct {
698698
// Inherits is the only field that cannot be overridden with --set
699699
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
700700

701-
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702-
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703-
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704-
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705-
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706-
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707-
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708-
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709-
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710-
CacheFrom []*buildflags.CacheOptionsEntry `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711-
CacheTo []*buildflags.CacheOptionsEntry `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712-
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713-
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714-
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715-
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716-
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717-
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718-
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719-
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720-
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721-
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722-
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723-
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724-
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
701+
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702+
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703+
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704+
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705+
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706+
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707+
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708+
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709+
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710+
CacheFrom *buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711+
CacheTo *buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712+
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713+
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714+
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715+
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716+
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717+
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718+
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719+
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720+
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721+
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722+
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723+
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724+
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
725725
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
726726

727727
// linked is a private field to mark a target used as a linked one
@@ -742,8 +742,12 @@ func (t *Target) normalize() {
742742
t.Secrets = removeDupes(t.Secrets)
743743
t.SSH = removeDupes(t.SSH)
744744
t.Platforms = removeDupesStr(t.Platforms)
745-
t.CacheFrom = removeDupes(t.CacheFrom)
746-
t.CacheTo = removeDupes(t.CacheTo)
745+
if t.CacheFrom != nil {
746+
t.CacheFrom.Normalize()
747+
}
748+
if t.CacheTo != nil {
749+
t.CacheTo.Normalize()
750+
}
747751
t.Outputs = removeDupes(t.Outputs)
748752
t.NoCacheFilter = removeDupesStr(t.NoCacheFilter)
749753
t.Ulimits = removeDupesStr(t.Ulimits)
@@ -824,7 +828,7 @@ func (t *Target) Merge(t2 *Target) {
824828
t.Platforms = t2.Platforms
825829
}
826830
if t2.CacheFrom != nil { // merge
827-
t.CacheFrom = append(t.CacheFrom, t2.CacheFrom...)
831+
t.CacheFrom = t.CacheFrom.Merge(t2.CacheFrom)
828832
}
829833
if t2.CacheTo != nil { // no merge
830834
t.CacheTo = t2.CacheTo
@@ -1338,17 +1342,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
13381342
}
13391343
}
13401344

1341-
cacheImports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheFrom))
1342-
for i, ci := range t.CacheFrom {
1343-
cacheImports[i] = ci.ToPB()
1345+
if t.CacheFrom != nil {
1346+
bo.CacheFrom = controllerapi.CreateCaches(t.CacheFrom.ToPB())
13441347
}
1345-
bo.CacheFrom = controllerapi.CreateCaches(cacheImports)
1346-
1347-
cacheExports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheTo))
1348-
for i, ce := range t.CacheTo {
1349-
cacheExports[i] = ce.ToPB()
1348+
if t.CacheTo != nil {
1349+
bo.CacheTo = controllerapi.CreateCaches(t.CacheTo.ToPB())
13501350
}
1351-
bo.CacheTo = controllerapi.CreateCaches(cacheExports)
13521351

13531352
outputs := make([]*controllerapi.ExportEntry, len(t.Outputs))
13541353
for i, output := range t.Outputs {
@@ -1591,9 +1590,13 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
15911590
return outputs, nil
15921591
}
15931592

1594-
func parseCacheArrValues(s []string) ([]*buildflags.CacheOptionsEntry, error) {
1593+
func parseCacheArrValues(s []string) (*buildflags.CacheOptions, error) {
15951594
outs := make([]*buildflags.CacheOptionsEntry, 0, len(s))
15961595
for _, in := range s {
1596+
if in == "" {
1597+
continue
1598+
}
1599+
15971600
if !strings.Contains(in, "=") {
15981601
// This is ref only format. Each field in the CSV is its own entry.
15991602
fields, err := csvvalue.Fields(in, nil)
@@ -1618,5 +1621,9 @@ func parseCacheArrValues(s []string) ([]*buildflags.CacheOptionsEntry, error) {
16181621
}
16191622
outs = append(outs, &out)
16201623
}
1621-
return outs, nil
1624+
1625+
if len(outs) == 0 {
1626+
return nil, nil
1627+
}
1628+
return &buildflags.CacheOptions{Entries: outs}, nil
16221629
}

bake/compose.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
353353
if err != nil {
354354
return err
355355
}
356-
t.CacheFrom = removeDupes(append(t.CacheFrom, cacheFrom...))
356+
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
357357
}
358358
if len(xb.CacheTo) > 0 {
359359
cacheTo, err := parseCacheArrValues(xb.CacheTo)
360360
if err != nil {
361361
return err
362362
}
363-
t.CacheTo = removeDupes(append(t.CacheTo, cacheTo...))
363+
t.CacheTo = t.CacheTo.Merge(cacheTo)
364364
}
365365
if len(xb.Secrets) > 0 {
366366
secrets, err := parseArrValue[buildflags.Secret](xb.Secrets)

bake/compose_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ secrets:
7474
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
7575
require.Equal(t, 1, len(c.Targets[1].Args))
7676
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
77-
require.Equal(t, []string{"type=local,src=path/to/cache"}, stringify(c.Targets[1].CacheFrom))
78-
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[1].CacheTo))
77+
require.Equal(t, []string{"type=local,src=path/to/cache"}, stringify(c.Targets[1].CacheFrom.Entries))
78+
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[1].CacheTo.Entries))
7979
require.Equal(t, "none", *c.Targets[1].NetworkMode)
8080
require.Equal(t, []string{"default", "key=path/to/key"}, stringify(c.Targets[1].SSH))
8181
require.Equal(t, []string{
@@ -336,8 +336,8 @@ services:
336336
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "CT_TAG": ptrstr("bar")}, c.Targets[0].Args)
337337
require.Equal(t, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"}, c.Targets[0].Tags)
338338
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[0].Platforms)
339-
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
340-
require.Equal(t, []string{"type=local,dest=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheTo))
339+
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom.Entries))
340+
require.Equal(t, []string{"type=local,dest=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheTo.Entries))
341341
require.Equal(t, []string{"default", "key=path/to/key", "other=path/to/otherkey"}, stringify(c.Targets[0].SSH))
342342
require.Equal(t, newBool(true), c.Targets[0].Pull)
343343
require.Equal(t, map[string]string{"alpine": "docker-image://alpine:3.13"}, c.Targets[0].Contexts)
@@ -383,8 +383,8 @@ services:
383383
require.NoError(t, err)
384384
require.Equal(t, 1, len(c.Targets))
385385
require.Equal(t, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
386-
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
387-
require.Equal(t, []string{"type=local,dest=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheTo))
386+
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom.Entries))
387+
require.Equal(t, []string{"type=local,dest=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheTo.Entries))
388388
require.Equal(t, []string{"default", "key=path/to/key"}, stringify(c.Targets[0].SSH))
389389
}
390390

bake/hcl_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
606606
target "app" {
607607
cache-from = [
608608
{ type = "registry", ref = "user/app:cache" },
609-
{ type = "local", src = "path/to/cache" },
609+
"type=local,src=path/to/cache",
610610
]
611611
612612
cache-to = [
@@ -634,8 +634,8 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
634634

635635
require.Equal(t, 1, len(c.Targets))
636636
require.Equal(t, []string{"type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
637-
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
638-
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
637+
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom.Entries))
638+
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo.Entries))
639639
require.Equal(t, []string{"id=mysecret,src=/local/secret", "id=mysecret2,env=TOKEN"}, stringify(c.Targets[0].Secrets))
640640
require.Equal(t, []string{"default", "key=path/to/key"}, stringify(c.Targets[0].SSH))
641641
}
@@ -649,7 +649,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
649649
target "app" {
650650
cache-from = [
651651
{ type = "registry", ref = "user/app:cache" },
652-
{ type = "local", src = "path/to/cache" },
652+
"type=local,src=path/to/cache",
653653
]
654654
655655
cache-to = [ target.app.cache-from[0] ]
@@ -674,7 +674,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
674674
output = [ "type=oci,dest=../${foo}.tar" ]
675675
676676
secret = [
677-
{ id = target.app.output[0].type, src = "/local/secret" },
677+
{ id = target.app.output[0].type, src = "/${target.app.cache-from[1].type}/secret" },
678678
]
679679
}
680680
`)
@@ -697,14 +697,14 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
697697

698698
app := findTarget(t, "app")
699699
require.Equal(t, []string{"type=oci,dest=../out.tar"}, stringify(app.Outputs))
700-
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(app.CacheFrom))
701-
require.Equal(t, []string{"user/app:cache"}, stringify(app.CacheTo))
700+
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(app.CacheFrom.Entries))
701+
require.Equal(t, []string{"user/app:cache"}, stringify(app.CacheTo.Entries))
702702
require.Equal(t, []string{"id=mysecret,src=/local/secret"}, stringify(app.Secrets))
703703
require.Equal(t, []string{"default", "key=path/to/oci"}, stringify(app.SSH))
704704

705705
web := findTarget(t, "web")
706706
require.Equal(t, []string{"type=oci,dest=../bar.tar"}, stringify(web.Outputs))
707-
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(web.CacheFrom))
707+
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(web.CacheFrom.Entries))
708708
require.Equal(t, []string{"id=oci,src=/local/secret"}, stringify(web.Secrets))
709709
}
710710

tests/bake.go

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func bakeCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
3232

3333
var bakeTests = []func(t *testing.T, sb integration.Sandbox){
3434
testBakePrint,
35+
testBakePrintSensitive,
3536
testBakeLocal,
3637
testBakeLocalMulti,
3738
testBakeRemote,
@@ -77,7 +78,8 @@ target "build" {
7778
HELLO = "foo"
7879
}
7980
}
80-
`)},
81+
`),
82+
},
8183
{
8284
"Compose",
8385
"compose.yml",
@@ -88,7 +90,8 @@ services:
8890
context: .
8991
args:
9092
HELLO: foo
91-
`)},
93+
`),
94+
},
9295
}
9396

9497
for _, tc := range testCases {
@@ -149,6 +152,124 @@ RUN echo "Hello ${HELLO}"
149152
}
150153
}
151154

155+
func testBakePrintSensitive(t *testing.T, sb integration.Sandbox) {
156+
testCases := []struct {
157+
name string
158+
f string
159+
dt []byte
160+
}{
161+
{
162+
"HCL",
163+
"docker-bake.hcl",
164+
[]byte(`
165+
target "build" {
166+
args = {
167+
HELLO = "foo"
168+
}
169+
170+
cache-from = [
171+
"type=gha",
172+
"type=s3,region=us-west-2,bucket=my_bucket,name=my_image",
173+
]
174+
}
175+
`),
176+
},
177+
{
178+
"Compose",
179+
"compose.yml",
180+
[]byte(`
181+
services:
182+
build:
183+
build:
184+
context: .
185+
args:
186+
HELLO: foo
187+
cache_from:
188+
- type=gha
189+
- type=s3,region=us-west-2,bucket=my_bucket,name=my_image
190+
`),
191+
},
192+
}
193+
194+
for _, tc := range testCases {
195+
t.Run(tc.name, func(t *testing.T) {
196+
dir := tmpdir(
197+
t,
198+
fstest.CreateFile(tc.f, tc.dt, 0600),
199+
fstest.CreateFile("Dockerfile", []byte(`
200+
FROM busybox
201+
ARG HELLO
202+
RUN echo "Hello ${HELLO}"
203+
`), 0600),
204+
)
205+
206+
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--print", "build"),
207+
withEnv(
208+
"ACTIONS_RUNTIME_TOKEN=sensitive_token",
209+
"ACTIONS_CACHE_URL=https://cache.github.com",
210+
"AWS_ACCESS_KEY_ID=definitely_dont_look_here",
211+
"AWS_SECRET_ACCESS_KEY=hackers_please_dont_steal",
212+
"AWS_SESSION_TOKEN=not_a_mitm_attack",
213+
),
214+
)
215+
stdout := bytes.Buffer{}
216+
stderr := bytes.Buffer{}
217+
cmd.Stdout = &stdout
218+
cmd.Stderr = &stderr
219+
require.NoError(t, cmd.Run(), stdout.String(), stderr.String())
220+
221+
var def struct {
222+
Group map[string]*bake.Group `json:"group,omitempty"`
223+
Target map[string]*bake.Target `json:"target"`
224+
}
225+
require.NoError(t, json.Unmarshal(stdout.Bytes(), &def))
226+
227+
require.Len(t, def.Group, 1)
228+
require.Contains(t, def.Group, "default")
229+
230+
require.Equal(t, []string{"build"}, def.Group["default"].Targets)
231+
require.Len(t, def.Target, 1)
232+
require.Contains(t, def.Target, "build")
233+
require.Equal(t, ".", *def.Target["build"].Context)
234+
require.Equal(t, "Dockerfile", *def.Target["build"].Dockerfile)
235+
require.Equal(t, map[string]*string{"HELLO": ptrstr("foo")}, def.Target["build"].Args)
236+
require.NotNil(t, def.Target["build"].CacheFrom)
237+
require.Len(t, def.Target["build"].CacheFrom.Entries, 2)
238+
239+
require.JSONEq(t, `{
240+
"group": {
241+
"default": {
242+
"targets": [
243+
"build"
244+
]
245+
}
246+
},
247+
"target": {
248+
"build": {
249+
"context": ".",
250+
"dockerfile": "Dockerfile",
251+
"args": {
252+
"HELLO": "foo"
253+
},
254+
"cache-from": [
255+
{
256+
"type": "gha"
257+
},
258+
{
259+
"type": "s3",
260+
"region": "us-west-2",
261+
"bucket": "my_bucket",
262+
"name": "my_image"
263+
}
264+
]
265+
}
266+
}
267+
}
268+
`, stdout.String())
269+
})
270+
}
271+
}
272+
152273
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
153274
dockerfile := []byte(`
154275
FROM scratch

0 commit comments

Comments
 (0)