Skip to content

Commit 16d8570

Browse files
authored
Merge pull request moby#3289 from jedevc/attestations-exporter-tar
Export attestations for tar exporter
2 parents 7804e2c + aa76a1e commit 16d8570

File tree

4 files changed

+264
-208
lines changed

4 files changed

+264
-208
lines changed

client/client_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7277,6 +7277,62 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) {
72777277
require.Equal(t, subjects, attest2.Subject)
72787278
}
72797279
})
7280+
7281+
t.Run("tar", func(t *testing.T) {
7282+
dir := t.TempDir()
7283+
out := filepath.Join(dir, "out.tar")
7284+
outW, err := os.Create(out)
7285+
require.NoError(t, err)
7286+
7287+
_, err = c.Build(sb.Context(), SolveOpt{
7288+
Exports: []ExportEntry{
7289+
{
7290+
Type: ExporterTar,
7291+
Output: fixedWriteCloser(outW),
7292+
Attrs: map[string]string{
7293+
"attestation-prefix": "test.",
7294+
},
7295+
},
7296+
},
7297+
}, "", frontend, nil)
7298+
require.NoError(t, err)
7299+
7300+
dt, err := os.ReadFile(out)
7301+
require.NoError(t, err)
7302+
7303+
m, err := testutil.ReadTarToMap(dt, false)
7304+
require.NoError(t, err)
7305+
7306+
for _, p := range ps {
7307+
var attest intoto.Statement
7308+
dt := m[path.Join(strings.ReplaceAll(platforms.Format(p), "/", "_"), "test.attestation.json")].Data
7309+
require.NoError(t, json.Unmarshal(dt, &attest))
7310+
7311+
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type)
7312+
require.Equal(t, "https://example.com/attestations/v1.0", attest.PredicateType)
7313+
require.Equal(t, map[string]interface{}{"success": true}, attest.Predicate)
7314+
7315+
require.Equal(t, []intoto.Subject{{
7316+
Name: "greeting",
7317+
Digest: result.ToDigestMap(digest.Canonical.FromString("hello " + platforms.Format(p) + "!")),
7318+
}}, attest.Subject)
7319+
7320+
var attest2 intoto.Statement
7321+
dt = m[path.Join(strings.ReplaceAll(platforms.Format(p), "/", "_"), "test.attestation2.json")].Data
7322+
require.NoError(t, json.Unmarshal(dt, &attest2))
7323+
7324+
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest2.Type)
7325+
require.Equal(t, "https://example.com/attestations2/v1.0", attest2.PredicateType)
7326+
require.Nil(t, attest2.Predicate)
7327+
subjects := []intoto.Subject{{
7328+
Name: "/attestation.json",
7329+
Digest: map[string]string{
7330+
"sha256": successDigest.Encoded(),
7331+
},
7332+
}}
7333+
require.Equal(t, subjects, attest2.Subject)
7334+
}
7335+
})
72807336
}
72817337

72827338
func testAttestationDefaultSubject(t *testing.T, sb integration.Sandbox) {

exporter/local/export.go

Lines changed: 18 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,18 @@ package local
33
import (
44
"context"
55
"encoding/json"
6-
"io"
7-
"io/fs"
86
"os"
9-
"path"
107
"strings"
118
"time"
129

13-
"github.com/docker/docker/pkg/idtools"
14-
intoto "github.com/in-toto/in-toto-golang/in_toto"
1510
"github.com/moby/buildkit/cache"
1611
"github.com/moby/buildkit/exporter"
17-
"github.com/moby/buildkit/exporter/attestation"
1812
"github.com/moby/buildkit/exporter/containerimage/exptypes"
1913
"github.com/moby/buildkit/exporter/util/epoch"
2014
"github.com/moby/buildkit/session"
2115
"github.com/moby/buildkit/session/filesync"
22-
"github.com/moby/buildkit/snapshot"
2316
"github.com/moby/buildkit/solver/result"
2417
"github.com/moby/buildkit/util/progress"
25-
"github.com/moby/buildkit/util/staticfs"
26-
digest "github.com/opencontainers/go-digest"
27-
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2818
"github.com/pkg/errors"
2919
"github.com/tonistiigi/fsutil"
3020
fstypes "github.com/tonistiigi/fsutil/types"
@@ -56,12 +46,17 @@ func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exp
5646
return nil, err
5747
}
5848

59-
i := &localExporterInstance{localExporter: e, epoch: tm}
49+
i := &localExporterInstance{
50+
localExporter: e,
51+
opts: CreateFSOpts{
52+
Epoch: tm,
53+
},
54+
}
6055

6156
for k, v := range opt {
6257
switch k {
6358
case keyAttestationPrefix:
64-
i.attestationPrefix = v
59+
i.opts.AttestationPrefix = v
6560
}
6661
}
6762

@@ -70,8 +65,7 @@ func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exp
7065

7166
type localExporterInstance struct {
7267
*localExporter
73-
epoch *time.Time
74-
attestationPrefix string
68+
opts CreateFSOpts
7569
}
7670

7771
func (e *localExporterInstance) Name() string {
@@ -86,11 +80,11 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
8680
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
8781
defer cancel()
8882

89-
if e.epoch == nil {
83+
if e.opts.Epoch == nil {
9084
if tm, ok, err := epoch.ParseSource(inp); err != nil {
9185
return nil, err
9286
} else if ok {
93-
e.epoch = tm
87+
e.opts.Epoch = tm
9488
}
9589
}
9690

@@ -114,127 +108,14 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
114108

115109
now := time.Now().Truncate(time.Second)
116110

117-
export := func(ctx context.Context, k string, p *ocispecs.Platform, ref cache.ImmutableRef, attestations []result.Attestation) func() error {
111+
export := func(ctx context.Context, k string, ref cache.ImmutableRef, attestations []result.Attestation) func() error {
118112
return func() error {
119-
var src string
120-
var err error
121-
var idmap *idtools.IdentityMapping
122-
if ref == nil {
123-
src, err = os.MkdirTemp("", "buildkit")
124-
if err != nil {
125-
return err
126-
}
127-
defer os.RemoveAll(src)
128-
} else {
129-
mount, err := ref.Mount(ctx, true, session.NewGroup(sessionID))
130-
if err != nil {
131-
return err
132-
}
133-
134-
lm := snapshot.LocalMounter(mount)
135-
136-
src, err = lm.Mount()
137-
if err != nil {
138-
return err
139-
}
140-
141-
idmap = mount.IdentityMapping()
142-
143-
defer lm.Unmount()
144-
}
145-
146-
walkOpt := &fsutil.WalkOpt{}
147-
var idMapFunc func(p string, st *fstypes.Stat) fsutil.MapResult
148-
if idmap != nil {
149-
idMapFunc = func(p string, st *fstypes.Stat) fsutil.MapResult {
150-
uid, gid, err := idmap.ToContainer(idtools.Identity{
151-
UID: int(st.Uid),
152-
GID: int(st.Gid),
153-
})
154-
if err != nil {
155-
return fsutil.MapResultExclude
156-
}
157-
st.Uid = uint32(uid)
158-
st.Gid = uint32(gid)
159-
return fsutil.MapResultKeep
160-
}
161-
}
162-
walkOpt.Map = func(p string, st *fstypes.Stat) fsutil.MapResult {
163-
res := fsutil.MapResultKeep
164-
if idMapFunc != nil {
165-
res = idMapFunc(p, st)
166-
}
167-
if e.epoch != nil {
168-
st.ModTime = e.epoch.UnixNano()
169-
}
170-
return res
171-
}
172-
173-
outputFS := fsutil.NewFS(src, walkOpt)
174-
175-
attestations, err = attestation.Unbundle(ctx, session.NewGroup(sessionID), inp.Refs, attestations)
113+
outputFS, cleanup, err := CreateFS(ctx, sessionID, k, ref, inp.Refs, attestations, now, e.opts)
176114
if err != nil {
177115
return err
178116
}
179-
if len(attestations) > 0 {
180-
subjects := []intoto.Subject{}
181-
err = outputFS.Walk(ctx, func(path string, info fs.FileInfo, err error) error {
182-
if err != nil {
183-
return err
184-
}
185-
if !info.Mode().IsRegular() {
186-
return nil
187-
}
188-
f, err := outputFS.Open(path)
189-
if err != nil {
190-
return err
191-
}
192-
defer f.Close()
193-
d := digest.Canonical.Digester()
194-
if _, err := io.Copy(d.Hash(), f); err != nil {
195-
return err
196-
}
197-
subjects = append(subjects, intoto.Subject{
198-
Name: path,
199-
Digest: result.ToDigestMap(d.Digest()),
200-
})
201-
return nil
202-
})
203-
if err != nil {
204-
return err
205-
}
206-
207-
stmts, err := attestation.MakeInTotoStatements(ctx, session.NewGroup(sessionID), inp.Refs, attestations, subjects)
208-
if err != nil {
209-
return err
210-
}
211-
stmtFS := staticfs.NewFS()
212-
213-
names := map[string]struct{}{}
214-
for i, stmt := range stmts {
215-
dt, err := json.Marshal(stmt)
216-
if err != nil {
217-
return errors.Wrap(err, "failed to marshal attestation")
218-
}
219-
220-
if attestations[i].Path == "" {
221-
return errors.New("attestation does not have set path")
222-
}
223-
name := e.attestationPrefix + path.Base(attestations[i].Path)
224-
if _, ok := names[name]; ok {
225-
return errors.Errorf("duplicate attestation path name %s", name)
226-
}
227-
names[name] = struct{}{}
228-
229-
st := fstypes.Stat{
230-
Mode: 0600,
231-
Path: name,
232-
ModTime: now.UnixNano(),
233-
}
234-
stmtFS.Add(name, st, dt)
235-
}
236-
237-
outputFS = staticfs.NewMergeFS(outputFS, stmtFS)
117+
if cleanup != nil {
118+
defer cleanup()
238119
}
239120

240121
lbl := "copying files"
@@ -244,8 +125,8 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
244125
Mode: uint32(os.ModeDir | 0755),
245126
Path: strings.Replace(k, "/", "_", -1),
246127
}
247-
if e.epoch != nil {
248-
st.ModTime = e.epoch.UnixNano()
128+
if e.opts.Epoch != nil {
129+
st.ModTime = e.opts.Epoch.UnixNano()
249130
}
250131

251132
outputFS, err = fsutil.SubDirFS([]fsutil.Dir{{FS: outputFS, Stat: st}})
@@ -270,13 +151,13 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
270151
if !ok {
271152
return nil, errors.Errorf("failed to find ref for ID %s", p.ID)
272153
}
273-
eg.Go(export(ctx, p.ID, &p.Platform, r, inp.Attestations[p.ID]))
154+
eg.Go(export(ctx, p.ID, r, inp.Attestations[p.ID]))
274155
if !isMap {
275156
break
276157
}
277158
}
278159
} else {
279-
eg.Go(export(ctx, "", nil, inp.Ref, nil))
160+
eg.Go(export(ctx, "", inp.Ref, nil))
280161
}
281162

282163
if err := eg.Wait(); err != nil {

0 commit comments

Comments
 (0)