From 3ddf50f9932bd77754b400838baba485ba1d228e Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 16 Dec 2022 13:51:12 -0800 Subject: [PATCH 01/39] fix indentation for in-toto and traces Signed-off-by: Tonis Tiigi (cherry picked from commit 61a1e33136f746cfb51f5926650c06fb3e8e1464) --- exporter/local/fs.go | 2 +- solver/llbsolver/provenance.go | 2 +- solver/llbsolver/solver.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/exporter/local/fs.go b/exporter/local/fs.go index bb90cfdd0d32..c5a524aae32f 100644 --- a/exporter/local/fs.go +++ b/exporter/local/fs.go @@ -132,7 +132,7 @@ func CreateFS(ctx context.Context, sessionID string, k string, ref cache.Immutab names := map[string]struct{}{} for i, stmt := range stmts { - dt, err := json.Marshal(stmt) + dt, err := json.MarshalIndent(stmt, "", " ") if err != nil { return nil, nil, errors.Wrap(err, "failed to marshal attestation") } diff --git a/solver/llbsolver/provenance.go b/solver/llbsolver/provenance.go index cf8b9b6b45bb..b30581c852d9 100644 --- a/solver/llbsolver/provenance.go +++ b/solver/llbsolver/provenance.go @@ -188,7 +188,7 @@ type resultRequests struct { platforms []exptypes.Platform } -// filterImagePlatforms filter out images that not for the current platform if an image existist for every platform in a result +// filterImagePlatforms filter out images that not for the current platform if an image exists for every platform in a result func (reqs *resultRequests) filterImagePlatforms(k string, imgs []provenance.ImageSource) []provenance.ImageSource { if len(reqs.platforms) == 0 { return imgs diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 453e656c762b..9084bf10cd93 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -353,6 +353,7 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend return err } enc := json.NewEncoder(w) + enc.SetIndent("", " ") for _, sp := range spans { if err := enc.Encode(sp); err != nil { return err From fa35a54905439285fd9736a2236dd6e3ebeadecd Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 19 Dec 2022 20:45:27 -0800 Subject: [PATCH 02/39] add possibility to override filename for provenance Signed-off-by: Tonis Tiigi (cherry picked from commit 4d4b56a648f01d4ed860e2c0ecf52f6dc51527ef) --- solver/llbsolver/proc/provenance.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index 4d8506b2987f..4577e40c7eee 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -42,6 +42,11 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { return nil, err } + filename := "provenance.json" + if v, ok := attrs["filename"]; ok { + filename = v + } + res.AddAttestation(p.ID, llbsolver.Attestation{ Kind: gatewaypb.AttestationKindInToto, Metadata: map[string][]byte{ @@ -51,7 +56,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { InToto: result.InTotoAttestation{ PredicateType: slsa02.PredicateSLSAProvenance, }, - Path: "provenance.json", + Path: filename, ContentFunc: func() ([]byte, error) { pr, err := pc.Predicate() if err != nil { From 96fe4518fbdcfed1d66098725ec00db02c5d1367 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 19 Dec 2022 20:49:10 -0800 Subject: [PATCH 03/39] provenance: move hermetic field into a correct struct Signed-off-by: Tonis Tiigi (cherry picked from commit 103a0a1f74f4279a7db9da793ba5793c8330670b) --- frontend/dockerfile/dockerfile_provenance_test.go | 4 ++-- solver/llbsolver/provenance/predicate.go | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/frontend/dockerfile/dockerfile_provenance_test.go b/frontend/dockerfile/dockerfile_provenance_test.go index f315ecb963a2..2d737511e570 100644 --- a/frontend/dockerfile/dockerfile_provenance_test.go +++ b/frontend/dockerfile/dockerfile_provenance_test.go @@ -198,7 +198,7 @@ RUN echo "ok" > /foo require.False(t, pred.Metadata.Completeness.Materials) require.False(t, pred.Metadata.Reproducible) - require.False(t, pred.Metadata.Completeness.Hermetic) + require.False(t, pred.Metadata.Hermetic) if mode == "max" || mode == "" { require.Equal(t, 2, len(pred.Metadata.BuildKitMetadata.Layers)) @@ -347,8 +347,8 @@ COPY myapp.Dockerfile / require.Equal(t, 0, len(pred.Invocation.Parameters.Locals)) require.True(t, pred.Metadata.Completeness.Materials) - require.True(t, pred.Metadata.Completeness.Hermetic) require.True(t, pred.Metadata.Completeness.Environment) + require.True(t, pred.Metadata.Hermetic) if isClient { require.False(t, pred.Metadata.Completeness.Parameters) diff --git a/solver/llbsolver/provenance/predicate.go b/solver/llbsolver/provenance/predicate.go index ec4f68cd598a..7608f5cfae9c 100644 --- a/solver/llbsolver/provenance/predicate.go +++ b/solver/llbsolver/provenance/predicate.go @@ -44,13 +44,8 @@ type Environment struct { type ProvenanceMetadata struct { slsa02.ProvenanceMetadata - Completeness ProvenanceComplete `json:"completeness"` - BuildKitMetadata BuildKitMetadata `json:"https://mobyproject.org/buildkit@v1#metadata,omitempty"` -} - -type ProvenanceComplete struct { - slsa02.ProvenanceComplete - Hermetic bool `json:"https://mobyproject.org/buildkit@v1#hermetic,omitempty"` + BuildKitMetadata BuildKitMetadata `json:"https://mobyproject.org/buildkit@v1#metadata,omitempty"` + Hermetic bool `json:"https://mobyproject.org/buildkit@v1#hermetic,omitempty"` } type BuildKitMetadata struct { @@ -211,14 +206,14 @@ func NewPredicate(c *Capture) (*ProvenancePredicate, error) { Materials: materials, }, Metadata: &ProvenanceMetadata{ - Completeness: ProvenanceComplete{ - ProvenanceComplete: slsa02.ProvenanceComplete{ + ProvenanceMetadata: slsa02.ProvenanceMetadata{ + Completeness: slsa02.ProvenanceComplete{ Parameters: c.Frontend != "", Environment: true, Materials: !incompleteMaterials, }, - Hermetic: !incompleteMaterials && !c.NetworkAccess, }, + Hermetic: !incompleteMaterials && !c.NetworkAccess, }, } From 142df0202938fb420bafb6f1b8e1eab4b78dda0a Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 19 Dec 2022 22:00:55 -0800 Subject: [PATCH 04/39] provenance: fix the order of the build steps Signed-off-by: Tonis Tiigi (cherry picked from commit ad85ce74b1edb21c00e04857487d873f46a63995) --- solver/llbsolver/provenance/buildconfig.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/solver/llbsolver/provenance/buildconfig.go b/solver/llbsolver/provenance/buildconfig.go index 3234d4b1d7bb..4d9bf85ec1ba 100644 --- a/solver/llbsolver/provenance/buildconfig.go +++ b/solver/llbsolver/provenance/buildconfig.go @@ -141,11 +141,6 @@ func toBuildSteps(def *pb.Definition) ([]BuildStep, map[digest.Digest]int, error if err != nil { return nil, nil, err } - for i := 0; i < len(dgsts)/2; i++ { - j := len(dgsts) - 1 - i - dgsts[i], dgsts[j] = dgsts[j], dgsts[i] - } - indexes := map[digest.Digest]int{} for i, dgst := range dgsts { indexes[dgst] = i @@ -179,7 +174,6 @@ func walkDigests(dgsts []digest.Digest, ops map[digest.Digest]*pb.Op, dgst diges if op == nil { return nil, errors.Errorf("invalid nil input %v", dgst) } - dgsts = append(dgsts, dgst) visited[dgst] = struct{}{} for _, inp := range op.Inputs { var err error @@ -188,5 +182,6 @@ func walkDigests(dgsts []digest.Digest, ops map[digest.Digest]*pb.Op, dgst diges return nil, err } } + dgsts = append(dgsts, dgst) return dgsts, nil } From d077c1922e8dacd684e8849675ea49de5d37a6a4 Mon Sep 17 00:00:00 2001 From: Erik Sipsma Date: Wed, 21 Dec 2022 11:26:36 -0800 Subject: [PATCH 05/39] sshforward: skip conn close on stream CloseSend. The GRPC docs on RecvMsg say: > RecvMsg blocks until it receives a message into m or the stream is > done. It returns io.EOF when the client has performed a CloseSend. > On any non-EOF error, the stream is aborted and the error contains > the RPC status. So if EOF is received that just means the client won't be sending anymore data. But it may still be expecting to read data, so we shouldn't close the conn yet. This was encountered in real life when forwarding a docker socket to a container, where it appears that the docker CLI closes its write side of the connection when requesting the stdout/stderr but then expects to read data after that. Signed-off-by: Erik Sipsma (cherry picked from commit eb9e2c974c9ae8d3f042c1d18359c75d50029415) --- session/sshforward/copy.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/session/sshforward/copy.go b/session/sshforward/copy.go index 8d05e9484144..936e1826af92 100644 --- a/session/sshforward/copy.go +++ b/session/sshforward/copy.go @@ -14,16 +14,19 @@ type Stream interface { } func Copy(ctx context.Context, conn io.ReadWriteCloser, stream Stream, closeStream func() error) error { + defer conn.Close() g, ctx := errgroup.WithContext(ctx) g.Go(func() (retErr error) { p := &BytesMessage{} for { if err := stream.RecvMsg(p); err != nil { - conn.Close() if err == io.EOF { + // indicates client performed CloseSend, but they may still be + // reading data, so don't close conn yet return nil } + conn.Close() return errors.WithStack(err) } select { From fd721885e1ad881ab691952f5af3267e1729f1d7 Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Wed, 2 Nov 2022 18:53:23 -0400 Subject: [PATCH 06/39] containerdexecutor: add network namespace callback In order to support identity mapping and user namespaces, the Moby project needs to defer the creation of a container's network namespace to the runtime and hook into the container lifecycle to configure the network namespace before the user binary is started. The standard way to do so is by configuring a `createRuntime` OCI lifecycle hook, in which the OCI runtime executes a specified process in the runtime environment after the container has been created and before it is started. In the case of Moby the network namespace needs to be configured from the daemon process, which necessitates that the hook process communicate with the daemon process. This is complicated and slow. All the hook process does is inform the daemon of the container's PID and wait until the daemon has finished applying the network namespace configuration. There is an alternative to the `createRuntime` OCI hook which containerd clients can take advantage of. The `container.NewTask` method is directly analogous to the OCI create operation, and the `task.Start` method is directly analogous to the OCI start operation. Any operations performed between the `NewTask` and `Start` calls are therefore directly analogous to `createRuntime` OCI hooks, without needing to execute any external processes! Provide a mechanism for network.Namespace instances to register a callback function which can be used to configure a container's network namespace instead of, or in addition to, `createRuntime` OCI hooks. Signed-off-by: Cory Snider (cherry picked from commit b5fdf90aee3aafb1dc852a37702c6432c1c1c2d5) --- executor/containerdexecutor/executor.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/executor/containerdexecutor/executor.go b/executor/containerdexecutor/executor.go index 75d9e819bae3..ac195c431588 100644 --- a/executor/containerdexecutor/executor.go +++ b/executor/containerdexecutor/executor.go @@ -45,6 +45,19 @@ type containerdExecutor struct { rootless bool } +// OnCreateRuntimer provides an alternative to OCI hooks for applying network +// configuration to a container. If the [network.Provider] returns a +// [network.Namespace] which also implements this interface, the containerd +// executor will run the callback at the appropriate point in the container +// lifecycle. +type OnCreateRuntimer interface { + // OnCreateRuntime is analogous to the createRuntime OCI hook. The + // function is called after the container is created, before the user + // process has been executed. The argument is the container PID in the + // runtime namespace. + OnCreateRuntime(pid uint32) error +} + // New creates a new executor backed by connection to containerd API func New(client *containerd.Client, root, cgroup string, networkProviders map[pb.NetMode]network.Provider, dnsConfig *oci.DNSConfig, apparmorProfile string, selinux bool, traceSocket string, rootless bool) executor.Executor { // clean up old hosts/resolv.conf file. ignore errors @@ -210,6 +223,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M } }() + if nn, ok := namespace.(OnCreateRuntimer); ok { + if err := nn.OnCreateRuntime(task.Pid()); err != nil { + return err + } + } + trace.SpanFromContext(ctx).AddEvent("Container created") err = w.runProcess(ctx, task, process.Resize, process.Signal, func() { startedOnce.Do(func() { From fb422c0576d684660285704adcf2eb4fd000d129 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 27 Dec 2022 18:10:34 +0100 Subject: [PATCH 07/39] frontend: fix testMultiStageImplicitFrom to account for busybox changes It looks like there's some changes between `busybox:1.34.0` and up; version 1.34.0 of the image did not have a `/usr/bin` directory (only `/usr/sbin`); docker run --rm -it busybox:1.34.0 ls -al /usr/ total 12 drwxr-xr-x 3 root root 4096 Sep 13 2021 . drwxr-xr-x 1 root root 4096 Dec 27 14:45 .. drwxr-xr-x 2 daemon daemon 4096 Sep 13 2021 sbin But 1.34.1 and up do; docker run --rm -it busybox:1.34.1 ls -al usr/ total 16 drwxr-xr-x 4 root root 4096 Dec 21 18:28 . drwxr-xr-x 1 root root 4096 Dec 27 14:44 .. drwxr-xr-x 2 root root 4096 Dec 21 18:28 bin drwxr-xr-x 2 daemon daemon 4096 Dec 21 18:28 sbin It's not immediately apparent what caused this change, or if it's in busybox itself, or in the official image only; https://github.com/mirror/busybox/compare/1_34_0...1_34_1 But either way, this change caused a test to fail: sandbox.go:238: time="2022-12-27T13:45:25.294022820Z" level=debug msg="> creating 4gr5bno8rj7l3k7h9jxe3jhal [/bin/sh -c mkdir /usr/bin && echo -n foo > /usr/bin/go]" span="[golang 2/2] RUN mkdir /usr/bin && echo -n foo > /usr/bin/go" sandbox.go:238: time="2022-12-27T13:45:25.433886983Z" level=debug msg="sandbox set key processing took 70.062631ms for container 5b4o358g2ryquk4s6ami38gqo" sandbox.go:238: mkdir: can't create directory '/usr/bin': File exists Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 34f9898f3112cac7c899e41d45ecdeb1502c3131) --- frontend/dockerfile/dockerfile_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 53a0d0cf0d15..336995cd4a11 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -3598,7 +3598,7 @@ COPY --from=busybox /etc/passwd test dockerfile = []byte(` FROM busybox AS golang -RUN mkdir /usr/bin && echo -n foo > /usr/bin/go +RUN mkdir -p /usr/bin && echo -n foo > /usr/bin/go FROM scratch COPY --from=golang /usr/bin/go go From b842260c50aee82ea7f37c1f382a86bcc1073f3a Mon Sep 17 00:00:00 2001 From: JordanGoasdoue Date: Wed, 21 Dec 2022 00:31:30 +0100 Subject: [PATCH 08/39] feat: allow ignoring remote cache-export error if failing Signed-off-by: JordanGoasdoue (cherry picked from commit e849b623c91df0149dfa133c27b2ed36fbd757ac) --- README.md | 5 ++ client/client_test.go | 108 +++++++++++++++++++++++++++++++++++++ control/control.go | 16 ++++++ control/control_test.go | 62 +++++++++++++++++++++ solver/llbsolver/solver.go | 13 ++--- 5 files changed, 198 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff2feb31317f..25e2fb01eed3 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,7 @@ buildctl build ... \ * `compression=`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true` * `compression-level=`: choose compression level for gzip, estargz (0-9) and zstd (0-22) * `force-compression=true`: forcibly apply `compression` option to all layers +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=registry` @@ -415,6 +416,7 @@ The directory layout conforms to OCI Image Spec v1.0. * `compression=`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`. * `compression-level=`: compression level for gzip, estargz (0-9) and zstd (0-22) * `force-compression=true`: forcibly apply `compression` option to all layers +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=local` @@ -449,6 +451,7 @@ in your workflow to expose the runtime. * `min`: only export layers for the resulting image * `max`: export all the layers of all intermediate steps * `scope=`: which scope cache object belongs to (default `buildkit`) +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=gha` @@ -496,6 +499,7 @@ Others options are: * `prefix=`: set global prefix to store / read files on s3 (default: empty) * `name=`: specify name of the manifest to use (default `buildkit`) * Multiple manifest names can be specified at the same time, separated by `;`. The standard use case is to use the git sha1 as name, and the branch name as duplicate, and load both with 2 `import-cache` commands. +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=s3` @@ -540,6 +544,7 @@ There are 2 options supported for Azure Blob Storage authentication: * `prefix=`: set global prefix to store / read files on the Azure Blob Storage container (``) (default: empty) * `name=`: specify name of the manifest to use (default: `buildkit`) * Multiple manifest names can be specified at the same time, separated by `;`. The standard use case is to use the git sha1 as name, and the branch name as duplicate, and load both with 2 `import-cache` commands. +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=azblob` diff --git a/client/client_test.go b/client/client_test.go index 62c61aa5641a..5d7dc1615a7d 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -168,6 +168,7 @@ func TestIntegration(t *testing.T) { testBuildInfoInline, testBuildInfoNoExport, testZstdLocalCacheExport, + testCacheExportIgnoreError, testZstdRegistryCacheImportExport, testZstdLocalCacheImportExport, testUncompressedLocalCacheImportExport, @@ -4351,6 +4352,113 @@ func testZstdLocalCacheExport(t *testing.T, sb integration.Sandbox) { require.Equal(t, dt[:4], []byte{0x28, 0xb5, 0x2f, 0xfd}) } +func testCacheExportIgnoreError(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureCacheExport) + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + busybox := llb.Image("busybox:latest") + cmd := `sh -e -c "echo -n ignore-error > data"` + + st := llb.Scratch() + st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st) + + def, err := st.Marshal(sb.Context()) + require.NoError(t, err) + + tests := map[string]struct { + Exports []ExportEntry + CacheExports []CacheOptionsEntry + expectedErrors []string + }{ + "local-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterLocal, + OutputDir: t.TempDir(), + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "local", + Attrs: map[string]string{ + "dest": "éèç", + }, + }, + }, + expectedErrors: []string{"failed to solve", "contains value with non-printable ASCII characters"}, + }, + "registry-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterImage, + Attrs: map[string]string{ + "name": "test-registry-ignore-error", + "push": "false", + }, + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "registry", + Attrs: map[string]string{ + "ref": "fake-url:5000/myrepo:buildcache", + }, + }, + }, + expectedErrors: []string{"failed to solve", "dial tcp: lookup fake-url", "no such host"}, + }, + "s3-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterLocal, + OutputDir: t.TempDir(), + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "s3", + Attrs: map[string]string{ + "endpoint_url": "http://fake-url:9000", + "bucket": "my-bucket", + "region": "us-east-1", + "access_key_id": "minioadmin", + "secret_access_key": "minioadmin", + "use_path_style": "true", + }, + }, + }, + expectedErrors: []string{"failed to solve", "dial tcp: lookup fake-url", "no such host"}, + }, + } + ignoreErrorValues := []bool{true, false} + for _, ignoreError := range ignoreErrorValues { + ignoreErrStr := strconv.FormatBool(ignoreError) + for n, test := range tests { + require.Equal(t, 1, len(test.Exports)) + require.Equal(t, 1, len(test.CacheExports)) + require.NotEmpty(t, test.CacheExports[0].Attrs) + test.CacheExports[0].Attrs["ignore-error"] = ignoreErrStr + testName := fmt.Sprintf("%s-%s", n, ignoreErrStr) + t.Run(testName, func(t *testing.T) { + _, err = c.Solve(sb.Context(), def, SolveOpt{ + Exports: test.Exports, + CacheExports: test.CacheExports, + }, nil) + if ignoreError { + require.NoError(t, err) + } else { + require.Error(t, err) + for _, errStr := range test.expectedErrors { + require.Contains(t, err.Error(), errStr) + } + } + }) + } + } +} + func testUncompressedLocalCacheImportExport(t *testing.T, sb integration.Sandbox) { integration.CheckFeatureCompat(t, sb, integration.FeatureCacheExport) dir := t.TempDir() diff --git a/control/control.go b/control/control.go index ef0f2cf59597..b8d204981007 100644 --- a/control/control.go +++ b/control/control.go @@ -3,6 +3,7 @@ package control import ( "context" "fmt" + "strconv" "sync" "sync/atomic" "time" @@ -368,6 +369,13 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (* } else { exp.CacheExportMode = exportMode } + if ignoreErrorStr, ok := e.Attrs["ignore-error"]; ok { + if ignoreError, supported := parseCacheExportIgnoreError(ignoreErrorStr); !supported { + bklog.G(ctx).Debugf("skipping invalid cache export ignore-error: %s", e.Attrs["ignore-error"]) + } else { + exp.IgnoreError = ignoreError + } + } cacheExporters = append(cacheExporters, exp) } @@ -550,6 +558,14 @@ func parseCacheExportMode(mode string) (solver.CacheExportMode, bool) { return solver.CacheExportModeMin, false } +func parseCacheExportIgnoreError(ignoreErrorStr string) (bool, bool) { + ignoreError, err := strconv.ParseBool(ignoreErrorStr) + if err != nil { + return false, false + } + return ignoreError, true +} + func toPBGCPolicy(in []client.PruneInfo) []*apitypes.GCPolicy { policy := make([]*apitypes.GCPolicy, 0, len(in)) for _, p := range in { diff --git a/control/control_test.go b/control/control_test.go index 1c5f499b613f..8707287e277a 100644 --- a/control/control_test.go +++ b/control/control_test.go @@ -84,3 +84,65 @@ func TestDuplicateCacheOptions(t *testing.T) { }) } } + +func TestParseCacheExportIgnoreError(t *testing.T) { + tests := map[string]struct { + expectedIgnoreError bool + expectedSupported bool + }{ + "": { + expectedIgnoreError: false, + expectedSupported: false, + }, + ".": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "fake": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "true": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "True": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "TRUE": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "truee": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "false": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "False": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "FALSE": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "ffalse": { + expectedIgnoreError: false, + expectedSupported: false, + }, + } + + for ignoreErrStr, test := range tests { + t.Run(ignoreErrStr, func(t *testing.T) { + ignoreErr, supported := parseCacheExportIgnoreError(ignoreErrStr) + t.Log("checking expectedIgnoreError") + require.Equal(t, ignoreErr, test.expectedIgnoreError) + t.Log("checking expectedSupported") + require.Equal(t, supported, test.expectedSupported) + }) + } +} diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 9084bf10cd93..310e824eff57 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -58,6 +58,7 @@ type ExporterRequest struct { type RemoteCacheExporter struct { remotecache.Exporter solver.CacheExportMode + IgnoreError bool } // ResolveWorkerFunc returns default worker for the temporary default non-distributed use cases @@ -570,7 +571,7 @@ func runCacheExporters(ctx context.Context, exporters []RemoteCacheExporter, j * func(exp RemoteCacheExporter, i int) { eg.Go(func() (err error) { id := fmt.Sprint(j.SessionID, "-cache-", i) - return inBuilderContext(ctx, j, exp.Exporter.Name(), id, func(ctx context.Context, _ session.Group) error { + err = inBuilderContext(ctx, j, exp.Exporter.Name(), id, func(ctx context.Context, _ session.Group) error { prepareDone := progress.OneOff(ctx, "preparing build cache for export") if err := result.EachRef(cached, inp, func(res solver.CachedResult, ref cache.ImmutableRef) error { ctx = withDescHandlerCacheOpts(ctx, ref) @@ -589,13 +590,13 @@ func runCacheExporters(ctx context.Context, exporters []RemoteCacheExporter, j * }); err != nil { return prepareDone(err) } - prepareDone(nil) resps[i], err = exp.Finalize(ctx) - if err != nil { - return err - } - return nil + return prepareDone(err) }) + if exp.IgnoreError { + err = nil + } + return err }) }(exp, i) } From 527b1a17626fe81b85418274b3f369240fcd15a6 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 27 Dec 2022 23:31:45 -0800 Subject: [PATCH 09/39] add cache stats to the build history API Signed-off-by: Tonis Tiigi (cherry picked from commit 853d56e56e0e810d358e34560f3ca193e9775004) --- api/services/control/control.pb.go | 352 +++++++++++++++++------------ api/services/control/control.proto | 3 +- cmd/buildctl/debug/monitor.go | 3 + solver/llbsolver/history.go | 37 ++- solver/llbsolver/solver.go | 10 +- 5 files changed, 259 insertions(+), 146 deletions(-) diff --git a/api/services/control/control.pb.go b/api/services/control/control.pb.go index 625b554a744d..db97db6141ec 100644 --- a/api/services/control/control.pb.go +++ b/api/services/control/control.pb.go @@ -1505,6 +1505,8 @@ type BuildHistoryRecord struct { Generation int32 `protobuf:"varint,12,opt,name=Generation,proto3" json:"Generation,omitempty"` Trace *Descriptor `protobuf:"bytes,13,opt,name=trace,proto3" json:"trace,omitempty"` Pinned bool `protobuf:"varint,14,opt,name=pinned,proto3" json:"pinned,omitempty"` + NumCachedSteps int32 `protobuf:"varint,15,opt,name=numCachedSteps,proto3" json:"numCachedSteps,omitempty"` + NumTotalSteps int32 `protobuf:"varint,16,opt,name=numTotalSteps,proto3" json:"numTotalSteps,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -1641,6 +1643,20 @@ func (m *BuildHistoryRecord) GetPinned() bool { return false } +func (m *BuildHistoryRecord) GetNumCachedSteps() int32 { + if m != nil { + return m.NumCachedSteps + } + return 0 +} + +func (m *BuildHistoryRecord) GetNumTotalSteps() int32 { + if m != nil { + return m.NumTotalSteps + } + return 0 +} + type UpdateBuildHistoryRequest struct { Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"` Pinned bool `protobuf:"varint,2,opt,name=Pinned,proto3" json:"Pinned,omitempty"` @@ -1962,146 +1978,148 @@ func init() { func init() { proto.RegisterFile("control.proto", fileDescriptor_0c5120591600887d) } var fileDescriptor_0c5120591600887d = []byte{ - // 2213 bytes of a gzipped FileDescriptorProto + // 2246 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x59, 0xcd, 0x6e, 0x1b, 0xc9, - 0x11, 0xf6, 0x90, 0x12, 0x7f, 0x8a, 0x94, 0x56, 0x6e, 0x7b, 0x8d, 0xc9, 0xc4, 0x2b, 0xc9, 0xb3, - 0x36, 0x22, 0x38, 0xf6, 0x50, 0xab, 0xac, 0x63, 0xaf, 0x9c, 0x38, 0x16, 0x45, 0x66, 0x2d, 0xc7, - 0x86, 0xb5, 0x2d, 0x79, 0x0d, 0x2c, 0xe0, 0x04, 0x23, 0xb2, 0x45, 0x0f, 0x34, 0x9c, 0x99, 0x74, - 0x37, 0xb5, 0xe6, 0x3e, 0x40, 0x80, 0xcd, 0x21, 0xc8, 0x25, 0xc8, 0x25, 0xf7, 0x9c, 0x72, 0xce, - 0x13, 0x04, 0xf0, 0x31, 0xe7, 0x3d, 0x38, 0x81, 0x1f, 0x20, 0xc8, 0x31, 0x97, 0x00, 0x8b, 0xfe, - 0x19, 0xb2, 0x49, 0x0e, 0x25, 0xca, 0xf6, 0x89, 0x5d, 0xdd, 0x55, 0x35, 0x55, 0xd5, 0xd5, 0xd5, - 0x5f, 0x35, 0x61, 0xa1, 0x15, 0x47, 0x9c, 0xc6, 0xa1, 0x97, 0xd0, 0x98, 0xc7, 0x68, 0xa9, 0x1b, - 0x1f, 0xf4, 0xbd, 0x83, 0x5e, 0x10, 0xb6, 0x8f, 0x02, 0xee, 0x1d, 0x7f, 0xe2, 0xdc, 0xec, 0x04, - 0xfc, 0x45, 0xef, 0xc0, 0x6b, 0xc5, 0xdd, 0x5a, 0x27, 0xee, 0xc4, 0x35, 0xc9, 0x78, 0xd0, 0x3b, - 0x94, 0x94, 0x24, 0xe4, 0x48, 0x29, 0x70, 0x56, 0x3a, 0x71, 0xdc, 0x09, 0xc9, 0x90, 0x8b, 0x07, - 0x5d, 0xc2, 0xb8, 0xdf, 0x4d, 0x34, 0xc3, 0x0d, 0x43, 0x9f, 0xf8, 0x58, 0x2d, 0xfd, 0x58, 0x8d, - 0xc5, 0xe1, 0x31, 0xa1, 0xb5, 0xe4, 0xa0, 0x16, 0x27, 0x4c, 0x73, 0xd7, 0xa6, 0x72, 0xfb, 0x49, - 0x50, 0xe3, 0xfd, 0x84, 0xb0, 0xda, 0xd7, 0x31, 0x3d, 0x22, 0x54, 0x0b, 0x6c, 0x8c, 0x9b, 0xab, - 0xec, 0xf1, 0x93, 0x80, 0xe9, 0x61, 0x8d, 0x26, 0xad, 0x1a, 0xe3, 0x3e, 0xef, 0xa5, 0x1f, 0xb9, - 0x75, 0x82, 0x49, 0x3d, 0xda, 0x22, 0x49, 0x1c, 0x06, 0xad, 0xbe, 0x30, 0x4c, 0x8d, 0x94, 0x98, - 0xfb, 0x3b, 0x0b, 0xaa, 0xbb, 0xb4, 0x17, 0x11, 0x4c, 0x7e, 0xdb, 0x23, 0x8c, 0xa3, 0x4b, 0x50, - 0x38, 0x0c, 0x42, 0x4e, 0xa8, 0x6d, 0xad, 0xe6, 0xd7, 0xca, 0x58, 0x53, 0x68, 0x09, 0xf2, 0x7e, - 0x18, 0xda, 0xb9, 0x55, 0x6b, 0xad, 0x84, 0xc5, 0x10, 0xad, 0x41, 0xf5, 0x88, 0x90, 0xa4, 0xd1, - 0xa3, 0x3e, 0x0f, 0xe2, 0xc8, 0xce, 0xaf, 0x5a, 0x6b, 0xf9, 0xfa, 0xdc, 0xab, 0xd7, 0x2b, 0x16, - 0x1e, 0x59, 0x41, 0x2e, 0x94, 0x05, 0x5d, 0xef, 0x73, 0xc2, 0xec, 0x39, 0x83, 0x6d, 0x38, 0xed, - 0x5e, 0x87, 0xa5, 0x46, 0xc0, 0x8e, 0x9e, 0x32, 0xbf, 0x73, 0x9a, 0x2d, 0xee, 0x43, 0x38, 0x6f, - 0xf0, 0xb2, 0x24, 0x8e, 0x18, 0x41, 0xb7, 0xa0, 0x40, 0x49, 0x2b, 0xa6, 0x6d, 0xc9, 0x5c, 0xd9, - 0xf8, 0xc8, 0x1b, 0x4f, 0x03, 0x4f, 0x0b, 0x08, 0x26, 0xac, 0x99, 0xdd, 0x3f, 0xe7, 0xa1, 0x62, - 0xcc, 0xa3, 0x45, 0xc8, 0xed, 0x34, 0x6c, 0x6b, 0xd5, 0x5a, 0x2b, 0xe3, 0xdc, 0x4e, 0x03, 0xd9, - 0x50, 0x7c, 0xdc, 0xe3, 0xfe, 0x41, 0x48, 0xb4, 0xef, 0x29, 0x89, 0x2e, 0xc2, 0xfc, 0x4e, 0xf4, - 0x94, 0x11, 0xe9, 0x78, 0x09, 0x2b, 0x02, 0x21, 0x98, 0xdb, 0x0b, 0xbe, 0x21, 0xca, 0x4d, 0x2c, - 0xc7, 0xc8, 0x81, 0xc2, 0xae, 0x4f, 0x49, 0xc4, 0xed, 0x79, 0xa1, 0xb7, 0x9e, 0xb3, 0x2d, 0xac, - 0x67, 0x50, 0x1d, 0xca, 0xdb, 0x94, 0xf8, 0x9c, 0xb4, 0xb7, 0xb8, 0x5d, 0x58, 0xb5, 0xd6, 0x2a, - 0x1b, 0x8e, 0xa7, 0x36, 0xd9, 0x4b, 0xf3, 0xcf, 0xdb, 0x4f, 0xf3, 0xaf, 0x5e, 0x7a, 0xf5, 0x7a, - 0xe5, 0xdc, 0x1f, 0xff, 0x25, 0x62, 0x37, 0x10, 0x43, 0xf7, 0x01, 0x1e, 0xf9, 0x8c, 0x3f, 0x65, - 0x52, 0x49, 0xf1, 0x54, 0x25, 0x73, 0x52, 0x81, 0x21, 0x83, 0x96, 0x01, 0x64, 0x10, 0xb6, 0xe3, - 0x5e, 0xc4, 0xed, 0x92, 0xb4, 0xdd, 0x98, 0x41, 0xab, 0x50, 0x69, 0x10, 0xd6, 0xa2, 0x41, 0x22, - 0xb7, 0xba, 0x2c, 0xc3, 0x63, 0x4e, 0x09, 0x0d, 0x2a, 0x82, 0xfb, 0xfd, 0x84, 0xd8, 0x20, 0x19, - 0x8c, 0x19, 0xb1, 0x97, 0x7b, 0x2f, 0x7c, 0x4a, 0xda, 0x76, 0x45, 0x86, 0x4b, 0x53, 0x22, 0xbe, - 0x2a, 0x12, 0xcc, 0xae, 0xca, 0x4d, 0x4e, 0x49, 0xf7, 0xf7, 0x45, 0xa8, 0xee, 0x89, 0xe3, 0x94, - 0xa6, 0xc3, 0x12, 0xe4, 0x31, 0x39, 0xd4, 0x7b, 0x23, 0x86, 0xc8, 0x03, 0x68, 0x90, 0xc3, 0x20, - 0x0a, 0xa4, 0x55, 0x39, 0xe9, 0xf8, 0xa2, 0x97, 0x1c, 0x78, 0xc3, 0x59, 0x6c, 0x70, 0x20, 0x07, - 0x4a, 0xcd, 0x97, 0x49, 0x4c, 0x45, 0x4a, 0xe5, 0xa5, 0x9a, 0x01, 0x8d, 0x9e, 0xc1, 0x42, 0x3a, - 0xde, 0xe2, 0x9c, 0x8a, 0x44, 0x15, 0x69, 0xf4, 0xc9, 0x64, 0x1a, 0x99, 0x46, 0x79, 0x23, 0x32, - 0xcd, 0x88, 0xd3, 0x3e, 0x1e, 0xd5, 0x23, 0x3c, 0xdc, 0x23, 0x8c, 0x09, 0x0b, 0xe5, 0xf6, 0xe3, - 0x94, 0x14, 0xe6, 0xfc, 0x92, 0xc6, 0x11, 0x27, 0x51, 0x5b, 0x6e, 0x7d, 0x19, 0x0f, 0x68, 0x61, - 0x4e, 0x3a, 0x56, 0xe6, 0x14, 0x67, 0x32, 0x67, 0x44, 0x46, 0x9b, 0x33, 0x32, 0x87, 0x36, 0x61, - 0x7e, 0xdb, 0x6f, 0xbd, 0x20, 0x72, 0x97, 0x2b, 0x1b, 0xcb, 0x93, 0x0a, 0xe5, 0xf2, 0x13, 0xb9, - 0xad, 0x4c, 0x1e, 0xd4, 0x73, 0x58, 0x89, 0xa0, 0x5f, 0x43, 0xb5, 0x19, 0xf1, 0x80, 0x87, 0xa4, - 0x2b, 0x77, 0xac, 0x2c, 0x76, 0xac, 0xbe, 0xf9, 0xdd, 0xeb, 0x95, 0x9f, 0x4e, 0x2d, 0x3f, 0x3d, - 0x1e, 0x84, 0x35, 0x62, 0x48, 0x79, 0x86, 0x0a, 0x3c, 0xa2, 0x0f, 0x7d, 0x05, 0x8b, 0xa9, 0xb1, - 0x3b, 0x51, 0xd2, 0xe3, 0xcc, 0x06, 0xe9, 0xf5, 0xc6, 0x8c, 0x5e, 0x2b, 0x21, 0xe5, 0xf6, 0x98, - 0x26, 0x11, 0xec, 0x9d, 0x88, 0x13, 0x1a, 0xf9, 0xa1, 0x4e, 0xc1, 0x01, 0x8d, 0x76, 0x44, 0xa6, - 0x89, 0x2a, 0xb9, 0x2b, 0x6b, 0xa3, 0x5d, 0x95, 0xa1, 0xb9, 0x36, 0xf9, 0x55, 0xb3, 0x96, 0x7a, - 0x8a, 0x19, 0x8f, 0x88, 0x3a, 0xf7, 0x01, 0x4d, 0xa6, 0x84, 0x48, 0xdd, 0x23, 0xd2, 0x4f, 0x53, - 0xf7, 0x88, 0xf4, 0x45, 0xf5, 0x38, 0xf6, 0xc3, 0x9e, 0xaa, 0x2a, 0x65, 0xac, 0x88, 0xcd, 0xdc, - 0x1d, 0x4b, 0x68, 0x98, 0xdc, 0xc5, 0x33, 0x69, 0xf8, 0x02, 0x2e, 0x64, 0x44, 0x24, 0x43, 0xc5, - 0x55, 0x53, 0xc5, 0xe4, 0xd1, 0x19, 0xaa, 0x74, 0xff, 0x96, 0x87, 0xaa, 0x99, 0x17, 0x68, 0x1d, - 0x2e, 0x28, 0x3f, 0x31, 0x39, 0x6c, 0x90, 0x84, 0x92, 0x96, 0x28, 0x46, 0x5a, 0x79, 0xd6, 0x12, - 0xda, 0x80, 0x8b, 0x3b, 0x5d, 0x3d, 0xcd, 0x0c, 0x91, 0x9c, 0x3c, 0xf6, 0x99, 0x6b, 0x28, 0x86, - 0x0f, 0x95, 0x2a, 0x19, 0x09, 0x43, 0x28, 0x2f, 0xf3, 0xe2, 0xb3, 0x93, 0x93, 0xd7, 0xcb, 0x94, - 0x55, 0xe9, 0x91, 0xad, 0x17, 0xfd, 0x1c, 0x8a, 0x6a, 0x21, 0x3d, 0xff, 0x1f, 0x9f, 0xfc, 0x09, - 0xa5, 0x2c, 0x95, 0x11, 0xe2, 0xca, 0x0f, 0x66, 0xcf, 0x9f, 0x41, 0x5c, 0xcb, 0x38, 0x0f, 0xc0, - 0x99, 0x6e, 0xf2, 0x59, 0x52, 0xc0, 0xfd, 0xab, 0x05, 0xe7, 0x27, 0x3e, 0x24, 0x2e, 0x27, 0x59, - 0x9e, 0x95, 0x0a, 0x39, 0x46, 0x0d, 0x98, 0x57, 0x05, 0x26, 0x27, 0x0d, 0xf6, 0x66, 0x30, 0xd8, - 0x33, 0xaa, 0x8b, 0x12, 0x76, 0xee, 0x00, 0xbc, 0x5d, 0xb2, 0xba, 0x7f, 0xb7, 0x60, 0x41, 0x1f, - 0x66, 0x7d, 0x93, 0xfb, 0xb0, 0x94, 0x1e, 0xa1, 0x74, 0x4e, 0xdf, 0xe9, 0xb7, 0xa6, 0xd6, 0x01, - 0xc5, 0xe6, 0x8d, 0xcb, 0x29, 0x1b, 0x27, 0xd4, 0x39, 0xdb, 0x69, 0x5e, 0x8d, 0xb1, 0x9e, 0xc9, - 0xf2, 0x2b, 0xb0, 0xb0, 0x27, 0x21, 0xd8, 0xd4, 0x0b, 0xca, 0xfd, 0xaf, 0x05, 0x8b, 0x29, 0x8f, - 0xf6, 0xee, 0x53, 0x28, 0x1d, 0x13, 0xca, 0xc9, 0x4b, 0xc2, 0xb4, 0x57, 0xf6, 0xa4, 0x57, 0x5f, - 0x4a, 0x0e, 0x3c, 0xe0, 0x44, 0x9b, 0x50, 0x52, 0x70, 0x8f, 0xa4, 0x1b, 0xb5, 0x3c, 0x4d, 0x4a, - 0x7f, 0x6f, 0xc0, 0x8f, 0x6a, 0x30, 0x17, 0xc6, 0x1d, 0xa6, 0xcf, 0xcc, 0x0f, 0xa7, 0xc9, 0x3d, - 0x8a, 0x3b, 0x58, 0x32, 0xa2, 0xbb, 0x50, 0xfa, 0xda, 0xa7, 0x51, 0x10, 0x75, 0xd2, 0x53, 0xb0, - 0x32, 0x4d, 0xe8, 0x99, 0xe2, 0xc3, 0x03, 0x01, 0x01, 0xa8, 0x0a, 0x6a, 0x0d, 0x3d, 0x84, 0x42, - 0x3b, 0xe8, 0x10, 0xc6, 0x55, 0x48, 0xea, 0x1b, 0xe2, 0x2e, 0xf9, 0xee, 0xf5, 0xca, 0x75, 0xe3, - 0xb2, 0x88, 0x13, 0x12, 0x09, 0xf8, 0xee, 0x07, 0x11, 0xa1, 0x02, 0xde, 0xde, 0x54, 0x22, 0x5e, - 0x43, 0xfe, 0x60, 0xad, 0x41, 0xe8, 0x0a, 0xd4, 0x95, 0x20, 0xeb, 0xc5, 0xdb, 0xe9, 0x52, 0x1a, - 0xc4, 0x31, 0x88, 0xfc, 0x2e, 0xd1, 0x10, 0x40, 0x8e, 0x05, 0x3e, 0x69, 0x89, 0x3c, 0x6f, 0x4b, - 0xe4, 0x56, 0xc2, 0x9a, 0x42, 0x9b, 0x50, 0x64, 0xdc, 0xa7, 0xa2, 0xe6, 0xcc, 0xcf, 0x08, 0xac, - 0x52, 0x01, 0x74, 0x0f, 0xca, 0xad, 0xb8, 0x9b, 0x84, 0x44, 0x48, 0x17, 0x66, 0x94, 0x1e, 0x8a, - 0x88, 0xd4, 0x23, 0x94, 0xc6, 0x54, 0x42, 0xba, 0x32, 0x56, 0x04, 0xba, 0x0d, 0x0b, 0x09, 0x8d, - 0x3b, 0x94, 0x30, 0xf6, 0x39, 0x8d, 0x7b, 0x89, 0xbe, 0xc8, 0xcf, 0x8b, 0xe2, 0xbd, 0x6b, 0x2e, - 0xe0, 0x51, 0x3e, 0xf7, 0x3f, 0x39, 0xa8, 0x9a, 0x29, 0x32, 0x81, 0x75, 0x1f, 0x42, 0x41, 0x25, - 0x9c, 0xca, 0xf5, 0xb7, 0x8b, 0xb1, 0xd2, 0x90, 0x19, 0x63, 0x1b, 0x8a, 0xad, 0x1e, 0x95, 0x40, - 0x58, 0xc1, 0xe3, 0x94, 0x14, 0x9e, 0xf2, 0x98, 0xfb, 0xa1, 0x8c, 0x71, 0x1e, 0x2b, 0x42, 0x60, - 0xe3, 0x41, 0xe7, 0x75, 0x36, 0x6c, 0x3c, 0x10, 0x33, 0xf7, 0xaf, 0xf8, 0x4e, 0xfb, 0x57, 0x3a, - 0xf3, 0xfe, 0xb9, 0xff, 0xb0, 0xa0, 0x3c, 0x38, 0x5b, 0x46, 0x74, 0xad, 0x77, 0x8e, 0xee, 0x48, - 0x64, 0x72, 0x6f, 0x17, 0x99, 0x4b, 0x50, 0x60, 0x9c, 0x12, 0xbf, 0xab, 0x3a, 0x37, 0xac, 0x29, - 0x51, 0xc5, 0xba, 0xac, 0x23, 0x77, 0xa8, 0x8a, 0xc5, 0xd0, 0xfd, 0x9f, 0x05, 0x0b, 0x23, 0xc7, - 0xfd, 0xbd, 0xfa, 0x72, 0x11, 0xe6, 0x43, 0x72, 0x4c, 0x54, 0x6f, 0x99, 0xc7, 0x8a, 0x10, 0xb3, - 0xec, 0x45, 0x4c, 0xb9, 0x34, 0xae, 0x8a, 0x15, 0x21, 0x6c, 0x6e, 0x13, 0xee, 0x07, 0xa1, 0xac, - 0x4b, 0x55, 0xac, 0x29, 0x61, 0x73, 0x8f, 0x86, 0x1a, 0x5f, 0x8b, 0x21, 0x72, 0x61, 0x2e, 0x88, - 0x0e, 0x63, 0x9d, 0x36, 0x12, 0xd9, 0x28, 0x9c, 0xb6, 0x13, 0x1d, 0xc6, 0x58, 0xae, 0xa1, 0x2b, - 0x50, 0xa0, 0x7e, 0xd4, 0x21, 0x29, 0xb8, 0x2e, 0x0b, 0x2e, 0x2c, 0x66, 0xb0, 0x5e, 0x70, 0x5d, - 0xa8, 0xca, 0xfe, 0xf4, 0x31, 0x61, 0xa2, 0x1b, 0x12, 0x69, 0xdd, 0xf6, 0xb9, 0x2f, 0xdd, 0xae, - 0x62, 0x39, 0x76, 0x6f, 0x00, 0x7a, 0x14, 0x30, 0xfe, 0x4c, 0xb6, 0xf0, 0xec, 0xb4, 0xe6, 0x75, - 0x0f, 0x2e, 0x8c, 0x70, 0xeb, 0x6b, 0xe1, 0x67, 0x63, 0xed, 0xeb, 0xd5, 0xc9, 0x8a, 0x2b, 0x5f, - 0x0a, 0x3c, 0x25, 0x38, 0xd6, 0xc5, 0x2e, 0x40, 0x45, 0xfa, 0xa5, 0xbe, 0xed, 0xfa, 0x50, 0x55, - 0xa4, 0x56, 0xfe, 0x05, 0x7c, 0x90, 0x2a, 0xfa, 0x92, 0x50, 0xd9, 0x8a, 0x58, 0x32, 0x2e, 0x3f, - 0x9a, 0xf6, 0x95, 0xfa, 0x28, 0x3b, 0x1e, 0x97, 0x77, 0x09, 0x5c, 0x90, 0x3c, 0x0f, 0x02, 0xc6, - 0x63, 0xda, 0x4f, 0xbd, 0x5e, 0x06, 0xd8, 0x6a, 0xf1, 0xe0, 0x98, 0x3c, 0x89, 0x42, 0x75, 0x8d, - 0x96, 0xb0, 0x31, 0x93, 0x5e, 0x91, 0xb9, 0x61, 0x0f, 0x77, 0x19, 0xca, 0x4d, 0x9f, 0x86, 0xfd, - 0xe6, 0xcb, 0x80, 0xeb, 0x56, 0x7a, 0x38, 0xe1, 0xfe, 0xc1, 0x82, 0xf3, 0xe6, 0x77, 0x9a, 0xc7, - 0xa2, 0x5c, 0xdc, 0x85, 0x39, 0x9e, 0xe2, 0x98, 0xc5, 0x2c, 0x27, 0x26, 0x44, 0x04, 0xd4, 0xc1, - 0x52, 0xc8, 0x88, 0xb4, 0x3a, 0x38, 0x57, 0x4f, 0x16, 0x1f, 0x8b, 0xf4, 0xff, 0x8b, 0x80, 0x26, - 0x97, 0x33, 0x7a, 0x53, 0xb3, 0xb9, 0xcb, 0x8d, 0x35, 0x77, 0xcf, 0xc7, 0x9b, 0x3b, 0x75, 0x35, - 0xdf, 0x9e, 0xc5, 0x92, 0x19, 0x5a, 0xbc, 0x3b, 0x50, 0x4e, 0xd1, 0x4d, 0x7a, 0x81, 0x3b, 0x93, - 0xaa, 0x07, 0x00, 0x68, 0xc8, 0x8c, 0xd6, 0xd2, 0x1b, 0x47, 0xdd, 0x75, 0x28, 0xad, 0x29, 0x34, - 0x69, 0x79, 0x1a, 0x57, 0xe8, 0x5b, 0xe8, 0xde, 0xd9, 0xde, 0x2d, 0xe6, 0xc6, 0xdf, 0x2c, 0xea, - 0x50, 0xd9, 0x4e, 0x0b, 0xe5, 0x19, 0x1e, 0x2d, 0x4c, 0x21, 0xb4, 0xae, 0x81, 0x8d, 0x2a, 0xcd, - 0x97, 0x27, 0x5d, 0x4c, 0x1f, 0x28, 0x62, 0xaa, 0x91, 0xcd, 0x61, 0x06, 0xb4, 0x2c, 0xcb, 0x00, - 0x6d, 0xce, 0x14, 0xfb, 0x19, 0xf1, 0x25, 0xfa, 0x0c, 0x0a, 0x98, 0xb0, 0x5e, 0xc8, 0xe5, 0x4b, - 0x48, 0x65, 0xe3, 0xca, 0x14, 0xed, 0x8a, 0x49, 0x9e, 0x55, 0x2d, 0x80, 0x7e, 0x05, 0x45, 0x35, - 0x62, 0x76, 0x65, 0x5a, 0xcb, 0x9f, 0x61, 0x99, 0x96, 0xd1, 0x0d, 0x85, 0xa6, 0xc4, 0x71, 0xfc, - 0x9c, 0x44, 0x44, 0xbf, 0xd0, 0x89, 0xb6, 0x76, 0x1e, 0x1b, 0x33, 0x68, 0x03, 0xe6, 0x39, 0xf5, - 0x5b, 0xc4, 0x5e, 0x98, 0x21, 0x84, 0x8a, 0x55, 0x14, 0xb6, 0x24, 0x88, 0x22, 0xd2, 0xb6, 0x17, - 0x15, 0x52, 0x52, 0xd4, 0x7b, 0xe8, 0x5b, 0xdf, 0x07, 0x2a, 0x77, 0x9e, 0x43, 0xd5, 0x8c, 0x45, - 0x86, 0xec, 0xed, 0xd1, 0xae, 0x77, 0x86, 0xbd, 0x31, 0x40, 0xff, 0x73, 0xf8, 0xc1, 0xd3, 0xa4, - 0xed, 0x73, 0x92, 0x55, 0xfd, 0x26, 0xab, 0xc0, 0x25, 0x28, 0xec, 0xaa, 0x60, 0xa9, 0xd7, 0x43, - 0x4d, 0x89, 0xf9, 0x06, 0x11, 0x79, 0xac, 0x4b, 0x9e, 0xa6, 0xdc, 0xcb, 0xe0, 0x64, 0xa9, 0x57, - 0xc1, 0x70, 0xff, 0x92, 0x03, 0x18, 0x6e, 0x08, 0xfa, 0x08, 0xa0, 0x4b, 0xda, 0x81, 0xff, 0x1b, - 0x3e, 0x6c, 0xea, 0xca, 0x72, 0x46, 0x76, 0x76, 0x43, 0xf8, 0x9d, 0x7b, 0x67, 0xf8, 0x8d, 0x60, - 0x8e, 0x05, 0xdf, 0x10, 0x0d, 0x15, 0xe4, 0x18, 0x3d, 0x81, 0x8a, 0x1f, 0x45, 0x31, 0x97, 0xa9, - 0x94, 0x36, 0xbc, 0x37, 0x4f, 0x4a, 0x21, 0x6f, 0x6b, 0xc8, 0xaf, 0x32, 0xd5, 0xd4, 0xe0, 0xdc, - 0x83, 0xa5, 0x71, 0x86, 0x33, 0x35, 0x64, 0xdf, 0x5a, 0xf0, 0xc1, 0xd8, 0xd6, 0xa1, 0x4f, 0x07, - 0x27, 0xd1, 0x9a, 0x21, 0xc5, 0xd3, 0x43, 0x78, 0x1f, 0xaa, 0x5b, 0x9c, 0x8b, 0xca, 0xa3, 0x7c, - 0x53, 0x2d, 0xd7, 0xc9, 0xb2, 0x23, 0x12, 0xee, 0x9f, 0xac, 0xe1, 0x5b, 0x63, 0x66, 0xdf, 0x7d, - 0x77, 0xb4, 0xef, 0xbe, 0x36, 0xbd, 0x40, 0xbf, 0xcf, 0x76, 0xfb, 0xfa, 0x2f, 0xe0, 0xc3, 0xcc, - 0xcb, 0x11, 0x55, 0xa0, 0xb8, 0xb7, 0xbf, 0x85, 0xf7, 0x9b, 0x8d, 0xa5, 0x73, 0xa8, 0x0a, 0xa5, - 0xed, 0x27, 0x8f, 0x77, 0x1f, 0x35, 0xf7, 0x9b, 0x4b, 0x96, 0x58, 0x6a, 0x34, 0xc5, 0xb8, 0xb1, - 0x94, 0xdb, 0xf8, 0xb6, 0x00, 0xc5, 0x6d, 0xf5, 0x7f, 0x0b, 0xda, 0x87, 0xf2, 0xe0, 0x21, 0x1e, - 0xb9, 0x19, 0xd1, 0x19, 0x7b, 0xd1, 0x77, 0x3e, 0x3e, 0x91, 0x47, 0x17, 0xcf, 0x07, 0x30, 0x2f, - 0xff, 0x92, 0x40, 0x19, 0x2d, 0xae, 0xf9, 0x5f, 0x85, 0x73, 0xf2, 0x13, 0xff, 0xba, 0x25, 0x34, - 0xc9, 0xf7, 0x81, 0x2c, 0x4d, 0xe6, 0x03, 0xa2, 0xb3, 0x72, 0xca, 0xc3, 0x02, 0x7a, 0x0c, 0x05, - 0xdd, 0x34, 0x65, 0xb1, 0x9a, 0xaf, 0x00, 0xce, 0xea, 0x74, 0x06, 0xa5, 0x6c, 0xdd, 0x42, 0x8f, - 0x07, 0x6f, 0xc2, 0x59, 0xa6, 0x99, 0x88, 0xd3, 0x39, 0x65, 0x7d, 0xcd, 0x5a, 0xb7, 0xd0, 0x57, - 0x50, 0x31, 0x30, 0x25, 0xca, 0x40, 0x34, 0x93, 0x00, 0xd5, 0xb9, 0x76, 0x0a, 0x97, 0xf6, 0xbc, - 0x09, 0x73, 0xf2, 0x20, 0x65, 0x04, 0xdb, 0x80, 0x9c, 0x59, 0x66, 0x8e, 0x40, 0xd0, 0x03, 0x05, - 0x92, 0x49, 0x64, 0x66, 0x1f, 0xba, 0x76, 0xda, 0xdd, 0x36, 0x35, 0x6d, 0x26, 0x92, 0x78, 0xdd, - 0x42, 0x31, 0xa0, 0xc9, 0xe2, 0x89, 0x7e, 0x9c, 0x91, 0x25, 0xd3, 0x2a, 0xb8, 0x73, 0x63, 0x36, - 0x66, 0xe5, 0x54, 0xbd, 0xfa, 0xea, 0xcd, 0xb2, 0xf5, 0xcf, 0x37, 0xcb, 0xd6, 0xbf, 0xdf, 0x2c, - 0x5b, 0x07, 0x05, 0x89, 0x5a, 0x7e, 0xf2, 0x7d, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xdc, 0xb1, - 0x33, 0x8f, 0x1c, 0x00, 0x00, + 0x11, 0xde, 0xe1, 0x3f, 0x8b, 0x94, 0x4c, 0xb7, 0xbd, 0xc6, 0x64, 0xe2, 0x95, 0xe4, 0x59, 0x3b, + 0x11, 0x1c, 0x7b, 0xa8, 0x65, 0xd6, 0xb1, 0x57, 0x4e, 0x1c, 0x8b, 0x22, 0xb3, 0x96, 0x63, 0xc3, + 0xda, 0x96, 0xbc, 0x06, 0x16, 0x70, 0x82, 0x11, 0xd9, 0xa2, 0x07, 0x1a, 0xce, 0x4c, 0xba, 0x9b, + 0x5a, 0x73, 0x1f, 0x20, 0xc0, 0xe6, 0x10, 0xe4, 0x12, 0xe4, 0x92, 0x7b, 0x4e, 0x39, 0xe7, 0x09, + 0x02, 0x18, 0xc8, 0x25, 0xe7, 0x3d, 0x38, 0x81, 0x1f, 0x20, 0xc8, 0x31, 0xc7, 0xa0, 0x7f, 0x86, + 0x1c, 0x92, 0x43, 0x89, 0xb2, 0x7d, 0x62, 0x57, 0x77, 0x55, 0x4d, 0x55, 0x75, 0x75, 0xf5, 0x57, + 0x4d, 0x58, 0xea, 0x84, 0x01, 0xa7, 0xa1, 0xef, 0x44, 0x34, 0xe4, 0x21, 0xaa, 0xf5, 0xc3, 0x83, + 0xa1, 0x73, 0x30, 0xf0, 0xfc, 0xee, 0x91, 0xc7, 0x9d, 0xe3, 0x4f, 0xac, 0x9b, 0x3d, 0x8f, 0xbf, + 0x18, 0x1c, 0x38, 0x9d, 0xb0, 0x5f, 0xef, 0x85, 0xbd, 0xb0, 0x2e, 0x19, 0x0f, 0x06, 0x87, 0x92, + 0x92, 0x84, 0x1c, 0x29, 0x05, 0xd6, 0x6a, 0x2f, 0x0c, 0x7b, 0x3e, 0x19, 0x73, 0x71, 0xaf, 0x4f, + 0x18, 0x77, 0xfb, 0x91, 0x66, 0xb8, 0x91, 0xd0, 0x27, 0x3e, 0x56, 0x8f, 0x3f, 0x56, 0x67, 0xa1, + 0x7f, 0x4c, 0x68, 0x3d, 0x3a, 0xa8, 0x87, 0x11, 0xd3, 0xdc, 0xf5, 0xb9, 0xdc, 0x6e, 0xe4, 0xd5, + 0xf9, 0x30, 0x22, 0xac, 0xfe, 0x75, 0x48, 0x8f, 0x08, 0xd5, 0x02, 0x8d, 0x69, 0x73, 0x95, 0x3d, + 0x6e, 0xe4, 0x31, 0x3d, 0xac, 0xd3, 0xa8, 0x53, 0x67, 0xdc, 0xe5, 0x83, 0xf8, 0x23, 0xb7, 0x4e, + 0x30, 0x69, 0x40, 0x3b, 0x24, 0x0a, 0x7d, 0xaf, 0x33, 0x14, 0x86, 0xa9, 0x91, 0x12, 0xb3, 0x7f, + 0x6b, 0x40, 0x75, 0x97, 0x0e, 0x02, 0x82, 0xc9, 0x6f, 0x06, 0x84, 0x71, 0x74, 0x09, 0x0a, 0x87, + 0x9e, 0xcf, 0x09, 0x35, 0x8d, 0xb5, 0xec, 0x7a, 0x19, 0x6b, 0x0a, 0xd5, 0x20, 0xeb, 0xfa, 0xbe, + 0x99, 0x59, 0x33, 0xd6, 0x4b, 0x58, 0x0c, 0xd1, 0x3a, 0x54, 0x8f, 0x08, 0x89, 0x5a, 0x03, 0xea, + 0x72, 0x2f, 0x0c, 0xcc, 0xec, 0x9a, 0xb1, 0x9e, 0x6d, 0xe6, 0x5e, 0xbd, 0x5e, 0x35, 0xf0, 0xc4, + 0x0a, 0xb2, 0xa1, 0x2c, 0xe8, 0xe6, 0x90, 0x13, 0x66, 0xe6, 0x12, 0x6c, 0xe3, 0x69, 0xfb, 0x3a, + 0xd4, 0x5a, 0x1e, 0x3b, 0x7a, 0xca, 0xdc, 0xde, 0x69, 0xb6, 0xd8, 0x0f, 0xe1, 0x7c, 0x82, 0x97, + 0x45, 0x61, 0xc0, 0x08, 0xba, 0x05, 0x05, 0x4a, 0x3a, 0x21, 0xed, 0x4a, 0xe6, 0x4a, 0xe3, 0x23, + 0x67, 0x3a, 0x0d, 0x1c, 0x2d, 0x20, 0x98, 0xb0, 0x66, 0xb6, 0xff, 0x94, 0x85, 0x4a, 0x62, 0x1e, + 0x2d, 0x43, 0x66, 0xa7, 0x65, 0x1a, 0x6b, 0xc6, 0x7a, 0x19, 0x67, 0x76, 0x5a, 0xc8, 0x84, 0xe2, + 0xe3, 0x01, 0x77, 0x0f, 0x7c, 0xa2, 0x7d, 0x8f, 0x49, 0x74, 0x11, 0xf2, 0x3b, 0xc1, 0x53, 0x46, + 0xa4, 0xe3, 0x25, 0xac, 0x08, 0x84, 0x20, 0xb7, 0xe7, 0x7d, 0x43, 0x94, 0x9b, 0x58, 0x8e, 0x91, + 0x05, 0x85, 0x5d, 0x97, 0x92, 0x80, 0x9b, 0x79, 0xa1, 0xb7, 0x99, 0x31, 0x0d, 0xac, 0x67, 0x50, + 0x13, 0xca, 0xdb, 0x94, 0xb8, 0x9c, 0x74, 0xb7, 0xb8, 0x59, 0x58, 0x33, 0xd6, 0x2b, 0x0d, 0xcb, + 0x51, 0x9b, 0xec, 0xc4, 0xf9, 0xe7, 0xec, 0xc7, 0xf9, 0xd7, 0x2c, 0xbd, 0x7a, 0xbd, 0xfa, 0xc1, + 0x1f, 0xfe, 0x25, 0x62, 0x37, 0x12, 0x43, 0xf7, 0x01, 0x1e, 0xb9, 0x8c, 0x3f, 0x65, 0x52, 0x49, + 0xf1, 0x54, 0x25, 0x39, 0xa9, 0x20, 0x21, 0x83, 0x56, 0x00, 0x64, 0x10, 0xb6, 0xc3, 0x41, 0xc0, + 0xcd, 0x92, 0xb4, 0x3d, 0x31, 0x83, 0xd6, 0xa0, 0xd2, 0x22, 0xac, 0x43, 0xbd, 0x48, 0x6e, 0x75, + 0x59, 0x86, 0x27, 0x39, 0x25, 0x34, 0xa8, 0x08, 0xee, 0x0f, 0x23, 0x62, 0x82, 0x64, 0x48, 0xcc, + 0x88, 0xbd, 0xdc, 0x7b, 0xe1, 0x52, 0xd2, 0x35, 0x2b, 0x32, 0x5c, 0x9a, 0x12, 0xf1, 0x55, 0x91, + 0x60, 0x66, 0x55, 0x6e, 0x72, 0x4c, 0xda, 0xbf, 0x2b, 0x42, 0x75, 0x4f, 0x1c, 0xa7, 0x38, 0x1d, + 0x6a, 0x90, 0xc5, 0xe4, 0x50, 0xef, 0x8d, 0x18, 0x22, 0x07, 0xa0, 0x45, 0x0e, 0xbd, 0xc0, 0x93, + 0x56, 0x65, 0xa4, 0xe3, 0xcb, 0x4e, 0x74, 0xe0, 0x8c, 0x67, 0x71, 0x82, 0x03, 0x59, 0x50, 0x6a, + 0xbf, 0x8c, 0x42, 0x2a, 0x52, 0x2a, 0x2b, 0xd5, 0x8c, 0x68, 0xf4, 0x0c, 0x96, 0xe2, 0xf1, 0x16, + 0xe7, 0x54, 0x24, 0xaa, 0x48, 0xa3, 0x4f, 0x66, 0xd3, 0x28, 0x69, 0x94, 0x33, 0x21, 0xd3, 0x0e, + 0x38, 0x1d, 0xe2, 0x49, 0x3d, 0xc2, 0xc3, 0x3d, 0xc2, 0x98, 0xb0, 0x50, 0x6e, 0x3f, 0x8e, 0x49, + 0x61, 0xce, 0x2f, 0x68, 0x18, 0x70, 0x12, 0x74, 0xe5, 0xd6, 0x97, 0xf1, 0x88, 0x16, 0xe6, 0xc4, + 0x63, 0x65, 0x4e, 0x71, 0x21, 0x73, 0x26, 0x64, 0xb4, 0x39, 0x13, 0x73, 0x68, 0x13, 0xf2, 0xdb, + 0x6e, 0xe7, 0x05, 0x91, 0xbb, 0x5c, 0x69, 0xac, 0xcc, 0x2a, 0x94, 0xcb, 0x4f, 0xe4, 0xb6, 0x32, + 0x79, 0x50, 0x3f, 0xc0, 0x4a, 0x04, 0xfd, 0x0a, 0xaa, 0xed, 0x80, 0x7b, 0xdc, 0x27, 0x7d, 0xb9, + 0x63, 0x65, 0xb1, 0x63, 0xcd, 0xcd, 0xef, 0x5e, 0xaf, 0xfe, 0x64, 0x6e, 0xf9, 0x19, 0x70, 0xcf, + 0xaf, 0x93, 0x84, 0x94, 0x93, 0x50, 0x81, 0x27, 0xf4, 0xa1, 0xaf, 0x60, 0x39, 0x36, 0x76, 0x27, + 0x88, 0x06, 0x9c, 0x99, 0x20, 0xbd, 0x6e, 0x2c, 0xe8, 0xb5, 0x12, 0x52, 0x6e, 0x4f, 0x69, 0x12, + 0xc1, 0xde, 0x09, 0x38, 0xa1, 0x81, 0xeb, 0xeb, 0x14, 0x1c, 0xd1, 0x68, 0x47, 0x64, 0x9a, 0xa8, + 0x92, 0xbb, 0xb2, 0x36, 0x9a, 0x55, 0x19, 0x9a, 0x6b, 0xb3, 0x5f, 0x4d, 0xd6, 0x52, 0x47, 0x31, + 0xe3, 0x09, 0x51, 0xeb, 0x3e, 0xa0, 0xd9, 0x94, 0x10, 0xa9, 0x7b, 0x44, 0x86, 0x71, 0xea, 0x1e, + 0x91, 0xa1, 0xa8, 0x1e, 0xc7, 0xae, 0x3f, 0x50, 0x55, 0xa5, 0x8c, 0x15, 0xb1, 0x99, 0xb9, 0x63, + 0x08, 0x0d, 0xb3, 0xbb, 0x78, 0x26, 0x0d, 0x5f, 0xc0, 0x85, 0x94, 0x88, 0xa4, 0xa8, 0xb8, 0x9a, + 0x54, 0x31, 0x7b, 0x74, 0xc6, 0x2a, 0xed, 0xbf, 0x66, 0xa1, 0x9a, 0xcc, 0x0b, 0xb4, 0x01, 0x17, + 0x94, 0x9f, 0x98, 0x1c, 0xb6, 0x48, 0x44, 0x49, 0x47, 0x14, 0x23, 0xad, 0x3c, 0x6d, 0x09, 0x35, + 0xe0, 0xe2, 0x4e, 0x5f, 0x4f, 0xb3, 0x84, 0x48, 0x46, 0x1e, 0xfb, 0xd4, 0x35, 0x14, 0xc2, 0x87, + 0x4a, 0x95, 0x8c, 0x44, 0x42, 0x28, 0x2b, 0xf3, 0xe2, 0xb3, 0x93, 0x93, 0xd7, 0x49, 0x95, 0x55, + 0xe9, 0x91, 0xae, 0x17, 0xfd, 0x0c, 0x8a, 0x6a, 0x21, 0x3e, 0xff, 0x1f, 0x9f, 0xfc, 0x09, 0xa5, + 0x2c, 0x96, 0x11, 0xe2, 0xca, 0x0f, 0x66, 0xe6, 0xcf, 0x20, 0xae, 0x65, 0xac, 0x07, 0x60, 0xcd, + 0x37, 0xf9, 0x2c, 0x29, 0x60, 0xff, 0xc5, 0x80, 0xf3, 0x33, 0x1f, 0x12, 0x97, 0x93, 0x2c, 0xcf, + 0x4a, 0x85, 0x1c, 0xa3, 0x16, 0xe4, 0x55, 0x81, 0xc9, 0x48, 0x83, 0x9d, 0x05, 0x0c, 0x76, 0x12, + 0xd5, 0x45, 0x09, 0x5b, 0x77, 0x00, 0xde, 0x2e, 0x59, 0xed, 0xbf, 0x19, 0xb0, 0xa4, 0x0f, 0xb3, + 0xbe, 0xc9, 0x5d, 0xa8, 0xc5, 0x47, 0x28, 0x9e, 0xd3, 0x77, 0xfa, 0xad, 0xb9, 0x75, 0x40, 0xb1, + 0x39, 0xd3, 0x72, 0xca, 0xc6, 0x19, 0x75, 0xd6, 0x76, 0x9c, 0x57, 0x53, 0xac, 0x67, 0xb2, 0xfc, + 0x0a, 0x2c, 0xed, 0x49, 0x08, 0x36, 0xf7, 0x82, 0xb2, 0xff, 0x6b, 0xc0, 0x72, 0xcc, 0xa3, 0xbd, + 0xfb, 0x14, 0x4a, 0xc7, 0x84, 0x72, 0xf2, 0x92, 0x30, 0xed, 0x95, 0x39, 0xeb, 0xd5, 0x97, 0x92, + 0x03, 0x8f, 0x38, 0xd1, 0x26, 0x94, 0x14, 0xdc, 0x23, 0xf1, 0x46, 0xad, 0xcc, 0x93, 0xd2, 0xdf, + 0x1b, 0xf1, 0xa3, 0x3a, 0xe4, 0xfc, 0xb0, 0xc7, 0xf4, 0x99, 0xf9, 0xfe, 0x3c, 0xb9, 0x47, 0x61, + 0x0f, 0x4b, 0x46, 0x74, 0x17, 0x4a, 0x5f, 0xbb, 0x34, 0xf0, 0x82, 0x5e, 0x7c, 0x0a, 0x56, 0xe7, + 0x09, 0x3d, 0x53, 0x7c, 0x78, 0x24, 0x20, 0x00, 0x55, 0x41, 0xad, 0xa1, 0x87, 0x50, 0xe8, 0x7a, + 0x3d, 0xc2, 0xb8, 0x0a, 0x49, 0xb3, 0x21, 0xee, 0x92, 0xef, 0x5e, 0xaf, 0x5e, 0x4f, 0x5c, 0x16, + 0x61, 0x44, 0x02, 0x01, 0xdf, 0x5d, 0x2f, 0x20, 0x54, 0xc0, 0xdb, 0x9b, 0x4a, 0xc4, 0x69, 0xc9, + 0x1f, 0xac, 0x35, 0x08, 0x5d, 0x9e, 0xba, 0x12, 0x64, 0xbd, 0x78, 0x3b, 0x5d, 0x4a, 0x83, 0x38, + 0x06, 0x81, 0xdb, 0x27, 0x1a, 0x02, 0xc8, 0xb1, 0xc0, 0x27, 0x1d, 0x91, 0xe7, 0x5d, 0x89, 0xdc, + 0x4a, 0x58, 0x53, 0x68, 0x13, 0x8a, 0x8c, 0xbb, 0x54, 0xd4, 0x9c, 0xfc, 0x82, 0xc0, 0x2a, 0x16, + 0x40, 0xf7, 0xa0, 0xdc, 0x09, 0xfb, 0x91, 0x4f, 0x84, 0x74, 0x61, 0x41, 0xe9, 0xb1, 0x88, 0x48, + 0x3d, 0x42, 0x69, 0x48, 0x25, 0xa4, 0x2b, 0x63, 0x45, 0xa0, 0xdb, 0xb0, 0x14, 0xd1, 0xb0, 0x47, + 0x09, 0x63, 0x9f, 0xd3, 0x70, 0x10, 0xe9, 0x8b, 0xfc, 0xbc, 0x28, 0xde, 0xbb, 0xc9, 0x05, 0x3c, + 0xc9, 0x67, 0xff, 0x27, 0x03, 0xd5, 0x64, 0x8a, 0xcc, 0x60, 0xdd, 0x87, 0x50, 0x50, 0x09, 0xa7, + 0x72, 0xfd, 0xed, 0x62, 0xac, 0x34, 0xa4, 0xc6, 0xd8, 0x84, 0x62, 0x67, 0x40, 0x25, 0x10, 0x56, + 0xf0, 0x38, 0x26, 0x85, 0xa7, 0x3c, 0xe4, 0xae, 0x2f, 0x63, 0x9c, 0xc5, 0x8a, 0x10, 0xd8, 0x78, + 0xd4, 0x79, 0x9d, 0x0d, 0x1b, 0x8f, 0xc4, 0x92, 0xfb, 0x57, 0x7c, 0xa7, 0xfd, 0x2b, 0x9d, 0x79, + 0xff, 0xec, 0xbf, 0x1b, 0x50, 0x1e, 0x9d, 0xad, 0x44, 0x74, 0x8d, 0x77, 0x8e, 0xee, 0x44, 0x64, + 0x32, 0x6f, 0x17, 0x99, 0x4b, 0x50, 0x60, 0x9c, 0x12, 0xb7, 0xaf, 0x3a, 0x37, 0xac, 0x29, 0x51, + 0xc5, 0xfa, 0xac, 0x27, 0x77, 0xa8, 0x8a, 0xc5, 0xd0, 0xfe, 0x9f, 0x01, 0x4b, 0x13, 0xc7, 0xfd, + 0xbd, 0xfa, 0x72, 0x11, 0xf2, 0x3e, 0x39, 0x26, 0xaa, 0xb7, 0xcc, 0x62, 0x45, 0x88, 0x59, 0xf6, + 0x22, 0xa4, 0x5c, 0x1a, 0x57, 0xc5, 0x8a, 0x10, 0x36, 0x77, 0x09, 0x77, 0x3d, 0x5f, 0xd6, 0xa5, + 0x2a, 0xd6, 0x94, 0xb0, 0x79, 0x40, 0x7d, 0x8d, 0xaf, 0xc5, 0x10, 0xd9, 0x90, 0xf3, 0x82, 0xc3, + 0x50, 0xa7, 0x8d, 0x44, 0x36, 0x0a, 0xa7, 0xed, 0x04, 0x87, 0x21, 0x96, 0x6b, 0xe8, 0x0a, 0x14, + 0xa8, 0x1b, 0xf4, 0x48, 0x0c, 0xae, 0xcb, 0x82, 0x0b, 0x8b, 0x19, 0xac, 0x17, 0x6c, 0x1b, 0xaa, + 0xb2, 0x3f, 0x7d, 0x4c, 0x98, 0xe8, 0x86, 0x44, 0x5a, 0x77, 0x5d, 0xee, 0x4a, 0xb7, 0xab, 0x58, + 0x8e, 0xed, 0x1b, 0x80, 0x1e, 0x79, 0x8c, 0x3f, 0x93, 0x2d, 0x3c, 0x3b, 0xad, 0x79, 0xdd, 0x83, + 0x0b, 0x13, 0xdc, 0xfa, 0x5a, 0xf8, 0xe9, 0x54, 0xfb, 0x7a, 0x75, 0xb6, 0xe2, 0xca, 0x97, 0x02, + 0x47, 0x09, 0x4e, 0x75, 0xb1, 0x4b, 0x50, 0x91, 0x7e, 0xa9, 0x6f, 0xdb, 0x2e, 0x54, 0x15, 0xa9, + 0x95, 0x7f, 0x01, 0xe7, 0x62, 0x45, 0x5f, 0x12, 0x2a, 0x5b, 0x11, 0x43, 0xc6, 0xe5, 0x87, 0xf3, + 0xbe, 0xd2, 0x9c, 0x64, 0xc7, 0xd3, 0xf2, 0x36, 0x81, 0x0b, 0x92, 0xe7, 0x81, 0xc7, 0x78, 0x48, + 0x87, 0xb1, 0xd7, 0x2b, 0x00, 0x5b, 0x1d, 0xee, 0x1d, 0x93, 0x27, 0x81, 0xaf, 0xae, 0xd1, 0x12, + 0x4e, 0xcc, 0xc4, 0x57, 0x64, 0x66, 0xdc, 0xc3, 0x5d, 0x86, 0x72, 0xdb, 0xa5, 0xfe, 0xb0, 0xfd, + 0xd2, 0xe3, 0xba, 0x95, 0x1e, 0x4f, 0xd8, 0xbf, 0x37, 0xe0, 0x7c, 0xf2, 0x3b, 0xed, 0x63, 0x51, + 0x2e, 0xee, 0x42, 0x8e, 0xc7, 0x38, 0x66, 0x39, 0xcd, 0x89, 0x19, 0x11, 0x01, 0x75, 0xb0, 0x14, + 0x4a, 0x44, 0x5a, 0x1d, 0x9c, 0xab, 0x27, 0x8b, 0x4f, 0x45, 0xfa, 0x1f, 0x25, 0x40, 0xb3, 0xcb, + 0x29, 0xbd, 0x69, 0xb2, 0xb9, 0xcb, 0x4c, 0x35, 0x77, 0xcf, 0xa7, 0x9b, 0x3b, 0x75, 0x35, 0xdf, + 0x5e, 0xc4, 0x92, 0x05, 0x5a, 0xbc, 0x3b, 0x50, 0x8e, 0xd1, 0x4d, 0x7c, 0x81, 0x5b, 0xb3, 0xaa, + 0x47, 0x00, 0x68, 0xcc, 0x8c, 0xd6, 0xe3, 0x1b, 0x47, 0xdd, 0x75, 0x28, 0xae, 0x29, 0x34, 0xea, + 0x38, 0x1a, 0x57, 0xe8, 0x5b, 0xe8, 0xde, 0xd9, 0xde, 0x2d, 0x72, 0xd3, 0x6f, 0x16, 0x4d, 0xa8, + 0x6c, 0xc7, 0x85, 0xf2, 0x0c, 0x8f, 0x16, 0x49, 0x21, 0xb4, 0xa1, 0x81, 0x8d, 0x2a, 0xcd, 0x97, + 0x67, 0x5d, 0x8c, 0x1f, 0x28, 0x42, 0xaa, 0x91, 0xcd, 0x61, 0x0a, 0xb4, 0x2c, 0xcb, 0x00, 0x6d, + 0x2e, 0x14, 0xfb, 0x05, 0xf1, 0x25, 0xfa, 0x0c, 0x0a, 0x98, 0xb0, 0x81, 0xcf, 0xe5, 0x4b, 0x48, + 0xa5, 0x71, 0x65, 0x8e, 0x76, 0xc5, 0x24, 0xcf, 0xaa, 0x16, 0x40, 0xbf, 0x84, 0xa2, 0x1a, 0x31, + 0xb3, 0x32, 0xaf, 0xe5, 0x4f, 0xb1, 0x4c, 0xcb, 0xe8, 0x86, 0x42, 0x53, 0xe2, 0x38, 0x7e, 0x4e, + 0x02, 0xa2, 0x5f, 0xe8, 0x44, 0x5b, 0x9b, 0xc7, 0x89, 0x19, 0xd4, 0x80, 0x3c, 0xa7, 0x6e, 0x87, + 0x98, 0x4b, 0x0b, 0x84, 0x50, 0xb1, 0x8a, 0xc2, 0x16, 0x79, 0x41, 0x40, 0xba, 0xe6, 0xb2, 0x42, + 0x4a, 0x8a, 0x42, 0x3f, 0x80, 0xe5, 0x60, 0xd0, 0x97, 0xcd, 0x42, 0x77, 0x8f, 0x93, 0x88, 0x99, + 0xe7, 0xe4, 0xf7, 0xa6, 0x66, 0xd1, 0x55, 0x58, 0x0a, 0x06, 0xfd, 0x7d, 0x71, 0xc3, 0x2b, 0xb6, + 0x9a, 0x64, 0x9b, 0x9c, 0x7c, 0x0f, 0x5d, 0xf0, 0xfb, 0xc0, 0xf8, 0xd6, 0x73, 0xa8, 0x26, 0x23, + 0x9b, 0x22, 0x7b, 0x7b, 0xb2, 0x87, 0x5e, 0x60, 0xa7, 0x13, 0x2d, 0xc4, 0x73, 0xf8, 0xde, 0xd3, + 0xa8, 0xeb, 0x72, 0x92, 0x56, 0x4b, 0x67, 0x6b, 0xca, 0x25, 0x28, 0xec, 0xaa, 0xd0, 0xab, 0xb7, + 0x48, 0x4d, 0x89, 0xf9, 0x16, 0x11, 0xa7, 0x42, 0x17, 0x50, 0x4d, 0xd9, 0x97, 0xc1, 0x4a, 0x53, + 0xaf, 0x82, 0x61, 0xff, 0x39, 0x03, 0x30, 0xde, 0x5e, 0xf4, 0x11, 0x40, 0x9f, 0x74, 0x3d, 0xf7, + 0xd7, 0x7c, 0xdc, 0x22, 0x96, 0xe5, 0x8c, 0xec, 0x13, 0xc7, 0x60, 0x3e, 0xf3, 0xce, 0x60, 0x1e, + 0x41, 0x8e, 0x79, 0xdf, 0x10, 0x0d, 0x3c, 0xe4, 0x18, 0x3d, 0x81, 0x8a, 0x1b, 0x04, 0x21, 0x97, + 0x89, 0x19, 0xb7, 0xcf, 0x37, 0x4f, 0x4a, 0x48, 0x67, 0x6b, 0xcc, 0xaf, 0xf2, 0x3e, 0xa9, 0xc1, + 0xba, 0x07, 0xb5, 0x69, 0x86, 0x33, 0xb5, 0x77, 0xdf, 0x1a, 0x70, 0x6e, 0x6a, 0xeb, 0xd0, 0xa7, + 0xa3, 0x73, 0x6d, 0x2c, 0x70, 0x60, 0xe2, 0x23, 0x7d, 0x1f, 0xaa, 0x5b, 0x9c, 0x8b, 0x3a, 0xa6, + 0x7c, 0x53, 0x0d, 0xdc, 0xc9, 0xb2, 0x13, 0x12, 0xf6, 0x1f, 0x8d, 0xf1, 0xcb, 0x65, 0x6a, 0x17, + 0x7f, 0x77, 0xb2, 0x8b, 0xbf, 0x36, 0xbf, 0xdc, 0xbf, 0xcf, 0xe6, 0xfd, 0xfa, 0xcf, 0xe1, 0xc3, + 0xd4, 0xab, 0x16, 0x55, 0xa0, 0xb8, 0xb7, 0xbf, 0x85, 0xf7, 0xdb, 0xad, 0xda, 0x07, 0xa8, 0x0a, + 0xa5, 0xed, 0x27, 0x8f, 0x77, 0x1f, 0xb5, 0xf7, 0xdb, 0x35, 0x43, 0x2c, 0xb5, 0xda, 0x62, 0xdc, + 0xaa, 0x65, 0x1a, 0xdf, 0x16, 0xa0, 0xb8, 0xad, 0xfe, 0xbd, 0x41, 0xfb, 0x50, 0x1e, 0x3d, 0xeb, + 0x23, 0x3b, 0x25, 0x3a, 0x53, 0xff, 0x0f, 0x58, 0x1f, 0x9f, 0xc8, 0xa3, 0x4b, 0xf1, 0x03, 0xc8, + 0xcb, 0x3f, 0x38, 0x50, 0x4a, 0xc3, 0x9c, 0xfc, 0xe7, 0xc3, 0x3a, 0xf9, 0x0f, 0x83, 0x0d, 0x43, + 0x68, 0x92, 0xaf, 0x0d, 0x69, 0x9a, 0x92, 0xcf, 0x91, 0xd6, 0xea, 0x29, 0xcf, 0x14, 0xe8, 0x31, + 0x14, 0x74, 0x0b, 0x96, 0xc6, 0x9a, 0x7c, 0x53, 0xb0, 0xd6, 0xe6, 0x33, 0x28, 0x65, 0x1b, 0x06, + 0x7a, 0x3c, 0x7a, 0x61, 0x4e, 0x33, 0x2d, 0x89, 0x5f, 0xad, 0x53, 0xd6, 0xd7, 0x8d, 0x0d, 0x03, + 0x7d, 0x05, 0x95, 0x04, 0x42, 0x45, 0x29, 0xf8, 0x68, 0x16, 0xee, 0x5a, 0xd7, 0x4e, 0xe1, 0xd2, + 0x9e, 0xb7, 0x21, 0x27, 0x0f, 0x52, 0x4a, 0xb0, 0x13, 0x00, 0x36, 0xcd, 0xcc, 0x09, 0x40, 0x7b, + 0xa0, 0x20, 0x37, 0x09, 0x92, 0xd9, 0x87, 0xae, 0x9d, 0x76, 0x53, 0xce, 0x4d, 0x9b, 0x99, 0x24, + 0xde, 0x30, 0x50, 0x08, 0x68, 0xb6, 0x78, 0xa2, 0x1f, 0xa5, 0x64, 0xc9, 0xbc, 0x0a, 0x6e, 0xdd, + 0x58, 0x8c, 0x59, 0x39, 0xd5, 0xac, 0xbe, 0x7a, 0xb3, 0x62, 0xfc, 0xf3, 0xcd, 0x8a, 0xf1, 0xef, + 0x37, 0x2b, 0xc6, 0x41, 0x41, 0x62, 0xa0, 0x1f, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xca, 0xd0, + 0xd7, 0x6e, 0xdd, 0x1c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -3945,6 +3963,18 @@ func (m *BuildHistoryRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.NumTotalSteps != 0 { + i = encodeVarintControl(dAtA, i, uint64(m.NumTotalSteps)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x80 + } + if m.NumCachedSteps != 0 { + i = encodeVarintControl(dAtA, i, uint64(m.NumCachedSteps)) + i-- + dAtA[i] = 0x78 + } if m.Pinned { i-- if m.Pinned { @@ -5071,6 +5101,12 @@ func (m *BuildHistoryRecord) Size() (n int) { if m.Pinned { n += 2 } + if m.NumCachedSteps != 0 { + n += 1 + sovControl(uint64(m.NumCachedSteps)) + } + if m.NumTotalSteps != 0 { + n += 2 + sovControl(uint64(m.NumTotalSteps)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -9989,6 +10025,44 @@ func (m *BuildHistoryRecord) Unmarshal(dAtA []byte) error { } } m.Pinned = bool(v != 0) + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumCachedSteps", wireType) + } + m.NumCachedSteps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowControl + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumCachedSteps |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumTotalSteps", wireType) + } + m.NumTotalSteps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowControl + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumTotalSteps |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipControl(dAtA[iNdEx:]) diff --git a/api/services/control/control.proto b/api/services/control/control.proto index 6bb692d839eb..9289d05cb15d 100644 --- a/api/services/control/control.proto +++ b/api/services/control/control.proto @@ -204,8 +204,9 @@ message BuildHistoryRecord { int32 Generation = 12; Descriptor trace = 13; bool pinned = 14; + int32 numCachedSteps = 15; + int32 numTotalSteps = 16; // TODO: tags - // TODO: steps/cache summary // TODO: unclipped logs } diff --git a/cmd/buildctl/debug/monitor.go b/cmd/buildctl/debug/monitor.go index 34bb9c319ecc..2cf83e9326bf 100644 --- a/cmd/buildctl/debug/monitor.go +++ b/cmd/buildctl/debug/monitor.go @@ -48,6 +48,9 @@ func monitor(clicontext *cli.Context) error { return err } fmt.Printf("event: %s ref:%s\n", ev.Type.String(), ev.Record.Ref) + if ev.Record.NumTotalSteps != 0 { + fmt.Printf(" cache: %d/%d\n", ev.Record.NumCachedSteps, ev.Record.NumTotalSteps) + } if ev.Record.Logs != nil { fmt.Printf(" logs: %s\n", ev.Record.Logs) } diff --git a/solver/llbsolver/history.go b/solver/llbsolver/history.go index 80bbb5ae7012..5c326f83fed3 100644 --- a/solver/llbsolver/history.go +++ b/solver/llbsolver/history.go @@ -44,6 +44,12 @@ type HistoryQueue struct { deleted map[string]struct{} } +type StatusImportResult struct { + Descriptor ocispecs.Descriptor + NumCachedSteps int + NumTotalSteps int +} + func NewHistoryQueue(opt HistoryQueueOpt) *HistoryQueue { if opt.CleanConfig == nil { opt.CleanConfig = &config.HistoryConfig{ @@ -433,7 +439,7 @@ func (w *Writer) Commit(ctx context.Context) (*ocispecs.Descriptor, func(), erro }, nil } -func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveStatus) (_ *ocispecs.Descriptor, _ func(), err error) { +func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveStatus) (_ *StatusImportResult, _ func(), err error) { defer func() { if ch == nil { return @@ -455,8 +461,19 @@ func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveSt } }() + vtxMap := make(map[digest.Digest]bool) + buf := make([]byte, 32*1024) for st := range ch { + for _, vtx := range st.Vertexes { + if _, ok := vtxMap[vtx.Digest]; !ok { + vtxMap[vtx.Digest] = false + } + if vtx.Cached { + vtxMap[vtx.Digest] = true + } + } + hdr := make([]byte, 4) for _, pst := range st.Marshal() { sz := pst.Size() @@ -479,7 +496,23 @@ func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveSt if err := bufW.Flush(); err != nil { return nil, nil, err } - return w.Commit(ctx) + desc, release, err := w.Commit(ctx) + if err != nil { + return nil, nil, err + } + + numCached := 0 + for _, cached := range vtxMap { + if cached { + numCached++ + } + } + + return &StatusImportResult{ + Descriptor: *desc, + NumCachedSteps: numCached, + NumTotalSteps: len(vtxMap), + }, release, nil } func (h *HistoryQueue) Listen(ctx context.Context, req *controlapi.BuildHistoryRequest, f func(*controlapi.BuildHistoryEvent) error) error { diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 310e824eff57..daeafba5f9ca 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -270,17 +270,19 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend } eg.Go(func() error { - desc, releaseStatus, err := s.history.ImportStatus(ctx2, ch) + st, releaseStatus, err := s.history.ImportStatus(ctx2, ch) if err != nil { return err } mu.Lock() releasers = append(releasers, releaseStatus) rec.Logs = &controlapi.Descriptor{ - Digest: desc.Digest, - Size_: desc.Size, - MediaType: desc.MediaType, + Digest: st.Descriptor.Digest, + Size_: st.Descriptor.Size, + MediaType: st.Descriptor.MediaType, } + rec.NumCachedSteps = int32(st.NumCachedSteps) + rec.NumTotalSteps = int32(st.NumTotalSteps) mu.Unlock() return nil }) From c24fc2875fd0e8e186d3e67ff14de8dc65faaa6e Mon Sep 17 00:00:00 2001 From: Gahl Saraf Date: Sat, 6 Aug 2022 21:47:13 +0300 Subject: [PATCH 10/39] Solve panic due to concurrent access to ExportSpans Signed-off-by: Gahl Saraf (cherry picked from commit afb01a74de439b3993ec383d5aae66c5b303fcaf) --- util/tracing/detect/detect.go | 22 ++++++++++++++++------ util/tracing/detect/threadsafe.go | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 util/tracing/detect/threadsafe.go diff --git a/util/tracing/detect/detect.go b/util/tracing/detect/detect.go index e005b14d0dfe..13e54bdefc01 100644 --- a/util/tracing/detect/detect.go +++ b/util/tracing/detect/detect.go @@ -73,21 +73,31 @@ func detectExporter() (sdktrace.SpanExporter, error) { return nil, nil } -func detect() error { - tp = trace.NewNoopTracerProvider() - +func getExporter() (sdktrace.SpanExporter, error) { exp, err := detectExporter() if err != nil { - return err + return nil, err + } + + if exp != nil { + exp = &threadSafeExporterWrapper{ + exporter: exp, + } } if Recorder != nil { Recorder.SpanExporter = exp exp = Recorder } + return exp, nil +} + +func detect() error { + tp = trace.NewNoopTracerProvider() - if exp == nil { - return nil + exp, err := getExporter() + if err != nil || exp == nil { + return err } // enable log with traceID when valid exporter diff --git a/util/tracing/detect/threadsafe.go b/util/tracing/detect/threadsafe.go new file mode 100644 index 000000000000..51d14448dfed --- /dev/null +++ b/util/tracing/detect/threadsafe.go @@ -0,0 +1,26 @@ +package detect + +import ( + "context" + "sync" + + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +// threadSafeExporterWrapper wraps an OpenTelemetry SpanExporter and makes it thread-safe. +type threadSafeExporterWrapper struct { + mu sync.Mutex + exporter sdktrace.SpanExporter +} + +func (tse *threadSafeExporterWrapper) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { + tse.mu.Lock() + defer tse.mu.Unlock() + return tse.exporter.ExportSpans(ctx, spans) +} + +func (tse *threadSafeExporterWrapper) Shutdown(ctx context.Context) error { + tse.mu.Lock() + defer tse.mu.Unlock() + return tse.exporter.Shutdown(ctx) +} From 64366ba4c933b799bcfc6433852be595e2bb86f9 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 16 Dec 2022 12:07:45 +0000 Subject: [PATCH 11/39] gateway: add addition check to prevent content func from being forwarded Signed-off-by: Justin Chadwell (cherry picked from commit e9b167cdb5f07b5d620a81905e77c7d38b440c9c) --- frontend/gateway/forwarder/forward.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/gateway/forwarder/forward.go b/frontend/gateway/forwarder/forward.go index 9ff531795abc..e13894ba37ed 100644 --- a/frontend/gateway/forwarder/forward.go +++ b/frontend/gateway/forwarder/forward.go @@ -67,6 +67,13 @@ func (c *bridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*cli if err != nil { return nil, c.wrapSolveError(err) } + for _, atts := range res.Attestations { + for _, att := range atts { + if att.ContentFunc != nil { + return nil, errors.Errorf("attestation callback cannot be sent through gateway") + } + } + } c.mu.Lock() cRes, err := result.ConvertResult(res, func(r solver.ResultProxy) (client.Reference, error) { @@ -178,6 +185,13 @@ func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, err if r == nil { return nil, nil } + for _, atts := range r.Attestations { + for _, att := range atts { + if att.ContentFunc != nil { + return nil, errors.Errorf("attestation callback cannot be sent through gateway") + } + } + } res, err := result.ConvertResult(r, func(r client.Reference) (solver.ResultProxy, error) { rr, ok := r.(*ref) @@ -186,13 +200,6 @@ func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, err } return rr.acquireResultProxy(), nil }) - for _, atts := range res.Attestations { - for _, att := range atts { - if att.ContentFunc != nil { - return nil, errors.Errorf("attestation callback cannot be sent through gateway") - } - } - } if err != nil { return nil, err } From 66371a00da49f3c6b5a2048df066c7eda6ac7023 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 14 Dec 2022 11:53:54 +0000 Subject: [PATCH 12/39] attestations: propogate metadata through unbundling This isn't anywhere at the moment, but we should be consistent here to help avoid any future logic issues. Signed-off-by: Justin Chadwell (cherry picked from commit 3959dc4caf912025c7d088df090182483aea2014) --- exporter/attestation/unbundle.go | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index a0dc21731b33..db4d6bc3b496 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -117,6 +117,7 @@ func unbundle(ctx context.Context, root string, bundle exporter.Attestation) ([] } unbundled = append(unbundled, exporter.Attestation{ Kind: gatewaypb.AttestationKindInToto, + Metadata: bundle.Metadata, Path: path.Join(bundle.Path, entry.Name()), ContentFunc: func() ([]byte, error) { return predicate, nil }, InToto: result.InTotoAttestation{ From 8c600ad514a7c274037b849770403889df6143ad Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 15 Dec 2022 17:05:08 +0000 Subject: [PATCH 13/39] attestations: ignore spdx parse errors Signed-off-by: Justin Chadwell (cherry picked from commit 244caf6bb97b8257251664e7204b3e7cd8e5d5ad) --- exporter/containerimage/attestations.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporter/containerimage/attestations.go b/exporter/containerimage/attestations.go index 680be58596d9..c9eef91c0c4a 100644 --- a/exporter/containerimage/attestations.go +++ b/exporter/containerimage/attestations.go @@ -43,7 +43,8 @@ func supplementSBOM(ctx context.Context, s session.Group, target cache.Immutable doc, err := decodeSPDX(content) if err != nil { - return att, err + // ignore decoding error + return att, nil } layers, err := newFileLayerFinder(target, targetRemote) From dbb58dbda28820e317f7bae1136cda3d2f18d4c5 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 15 Dec 2022 16:24:20 +0000 Subject: [PATCH 14/39] result: change reason types to strings Signed-off-by: Justin Chadwell (cherry picked from commit 3bc2aa6e2325ee901c967b157205a564fa313ac5) --- frontend/attestations/sbom/sbom.go | 2 +- solver/llbsolver/proc/provenance.go | 2 +- solver/result/attestation.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/attestations/sbom/sbom.go b/frontend/attestations/sbom/sbom.go index 54e8e316a2a2..4d86f74140bf 100644 --- a/frontend/attestations/sbom/sbom.go +++ b/frontend/attestations/sbom/sbom.go @@ -88,7 +88,7 @@ func CreateSBOMScanner(ctx context.Context, resolver llb.ImageMetaResolver, scan Kind: gatewaypb.AttestationKindBundle, Ref: stsbom, Metadata: map[string][]byte{ - result.AttestationReasonKey: result.AttestationReasonSBOM, + result.AttestationReasonKey: []byte(result.AttestationReasonSBOM), }, InToto: result.InTotoAttestation{ PredicateType: intoto.PredicateSPDX, diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index 4577e40c7eee..e6116c2eaafd 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -50,7 +50,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { res.AddAttestation(p.ID, llbsolver.Attestation{ Kind: gatewaypb.AttestationKindInToto, Metadata: map[string][]byte{ - result.AttestationReasonKey: result.AttestationReasonProvenance, + result.AttestationReasonKey: []byte(result.AttestationReasonProvenance), result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(inlineOnly)), }, InToto: result.InTotoAttestation{ diff --git a/solver/result/attestation.go b/solver/result/attestation.go index 6fa8403db67c..450016ffab70 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -12,9 +12,9 @@ const ( AttestationInlineOnlyKey = "inline-only" ) -var ( - AttestationReasonSBOM = []byte("sbom") - AttestationReasonProvenance = []byte("provenance") +const ( + AttestationReasonSBOM = "sbom" + AttestationReasonProvenance = "provenance" ) type Attestation[T any] struct { From 344d18b83f342c2e47be0965e172320650748000 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 15 Dec 2022 16:23:05 +0000 Subject: [PATCH 15/39] exporter: make attestation validation public Signed-off-by: Justin Chadwell (cherry picked from commit 3fab94ff29c2ec00ea82368e2f25b0802a92fa92) --- exporter/attestation/unbundle.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index db4d6bc3b496..ab4d8d6094fa 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -65,11 +65,10 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati for _, atts := range unbundled { joined = append(joined, atts...) } - for _, att := range joined { - if err := validate(att); err != nil { + + if err := Validate(joined); err != nil { return nil, err } - } return joined, nil } @@ -129,6 +128,15 @@ func unbundle(ctx context.Context, root string, bundle exporter.Attestation) ([] return unbundled, nil } +func Validate(atts []exporter.Attestation) error { + for _, att := range atts { + if err := validate(att); err != nil { + return err + } + } + return nil +} + func validate(att exporter.Attestation) error { if att.Path == "" { return errors.New("attestation does not have set path") From 195665188b776a269c15761570fbbc72d7b5b685 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 16 Dec 2022 12:06:13 +0000 Subject: [PATCH 16/39] attestation: validate attestations before unbundling as well Signed-off-by: Justin Chadwell (cherry picked from commit 24807f53e2bc31276d32640280859fd9b7fd09da) --- exporter/attestation/unbundle.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index ab4d8d6094fa..ebf414973d23 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -20,6 +20,10 @@ import ( // Unbundle iterates over all provided result attestations and un-bundles any // bundled attestations by loading them from the provided refs map. func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestation) ([]exporter.Attestation, error) { + if err := Validate(bundled); err != nil { + return nil, err + } + eg, ctx := errgroup.WithContext(ctx) unbundled := make([][]exporter.Attestation, len(bundled)) @@ -138,7 +142,7 @@ func Validate(atts []exporter.Attestation) error { } func validate(att exporter.Attestation) error { - if att.Path == "" { + if att.Kind != gatewaypb.AttestationKindBundle && att.Path == "" { return errors.New("attestation does not have set path") } if att.Ref == nil && att.ContentFunc == nil { From bd5eefc8150d451cfc50249c4c294c69586a99d8 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 16 Dec 2022 12:07:17 +0000 Subject: [PATCH 17/39] attestation: forbid provenance attestations from frontend Signed-off-by: Justin Chadwell (cherry picked from commit 13a24b7668f2b5d35a3fdfaf41567a6fc95c4f8c) --- exporter/attestation/unbundle.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index ebf414973d23..498b13624bad 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -5,6 +5,7 @@ import ( "encoding/json" "os" "path" + "strings" "github.com/containerd/continuity/fs" intoto "github.com/in-toto/in-toto-golang/in_toto" @@ -32,6 +33,12 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati eg.Go(func() error { switch att.Kind { case gatewaypb.AttestationKindInToto: + if strings.HasPrefix(att.InToto.PredicateType, "https://slsa.dev/provenance/") { + if att.ContentFunc == nil { + // provenance may only be set buildkit-side using ContentFunc + return errors.New("frontend may not set provenance attestations") + } + } unbundled[i] = append(unbundled[i], att) case gatewaypb.AttestationKindBundle: if att.ContentFunc != nil { @@ -56,6 +63,11 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati if err != nil { return err } + for _, att := range atts { + if strings.HasPrefix(att.InToto.PredicateType, "https://slsa.dev/provenance/") { + return errors.New("frontend may not bundle provenance attestations") + } + } unbundled[i] = append(unbundled[i], atts...) } return nil @@ -71,8 +83,8 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati } if err := Validate(joined); err != nil { - return nil, err - } + return nil, err + } return joined, nil } From 8a5c0709ecb60e741caf340dc3f2e7e13657a90f Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 4 Jan 2023 10:14:15 +0000 Subject: [PATCH 18/39] vendor: update spdx/tools-golang to d6f58551be3f Followup to 9c7c061e95e20f5548674ed90bf0269e5e7e1441. Remove the temporary replacement for jedevc/spdx-tools-golang, since the required commits have been merged upstream. Signed-off-by: Justin Chadwell (cherry picked from commit 3ea031c605fbbcdaf539df1a19d180f808e7cd52) --- go.mod | 4 +--- go.sum | 4 ++-- vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go | 2 +- vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go | 2 +- vendor/modules.txt | 3 +-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2a831069b6a8..6d723f35befd 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/pkg/profile v1.5.0 github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 github.com/sirupsen/logrus v1.9.0 - github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342 + github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f github.com/stretchr/testify v1.8.0 github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 @@ -152,5 +152,3 @@ require ( golang.org/x/text v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/spdx/tools-golang => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a diff --git a/go.sum b/go.sum index 5707a032f27b..b150443fcca1 100644 --- a/go.sum +++ b/go.sum @@ -912,8 +912,6 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a h1:oEb/YxUfXfGhRJVEg4yBfSnB6ZgS9aBu4RAM0fB9XsA= -github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= @@ -1318,6 +1316,8 @@ github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34c github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f h1:9B623Cfs+mclYK6dsae7gLSwuIBHvlgmEup87qpqsAQ= +github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= diff --git a/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go b/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go index 77d9c08f5851..2d99e0456b0b 100644 --- a/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go +++ b/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go @@ -50,7 +50,7 @@ type Package struct { // 7.9: Package Verification Code PackageVerificationCode common.PackageVerificationCode `json:"packageVerificationCode"` - // 7.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5 + // 7.10: Package Checksum: may have keys for SHA1, SHA256, SHA512 and/or MD5 // Cardinality: optional, one or many PackageChecksums []common.Checksum `json:"checksums,omitempty"` diff --git a/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go b/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go index 86ab08f14642..b9d5b9515b48 100644 --- a/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go +++ b/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go @@ -51,7 +51,7 @@ type Package struct { // Cardinality: if FilesAnalyzed == true must be present, if FilesAnalyzed == false must be omitted PackageVerificationCode *common.PackageVerificationCode `json:"packageVerificationCode,omitempty"` - // 7.10: Package Checksum: may have keys for SHA1, SHA256, MD5, SHA3-256, SHA3-384, SHA3-512, BLAKE2b-256, BLAKE2b-384, BLAKE2b-512, BLAKE3, ADLER32 + // 7.10: Package Checksum: may have keys for SHA1, SHA256, SHA512, MD5, SHA3-256, SHA3-384, SHA3-512, BLAKE2b-256, BLAKE2b-384, BLAKE2b-512, BLAKE3, ADLER32 // Cardinality: optional, one or many PackageChecksums []common.Checksum `json:"checksums,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 004bf96ad171..dc7358d0a9dc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -666,7 +666,7 @@ github.com/shibumi/go-pathspec # github.com/sirupsen/logrus v1.9.0 ## explicit; go 1.13 github.com/sirupsen/logrus -# github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342 => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a +# github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f ## explicit; go 1.13 github.com/spdx/tools-golang/json github.com/spdx/tools-golang/spdx/common @@ -928,4 +928,3 @@ google.golang.org/protobuf/types/pluginpb # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# github.com/spdx/tools-golang => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a From 0dec23a258bdad557678c4f9b4074810403f8359 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 8 Dec 2022 11:53:18 +0000 Subject: [PATCH 19/39] docs: add slsa provenance documentation Signed-off-by: Justin Chadwell (cherry picked from commit b7a4bca657e44e0bb69d0c1138fb7aaa8f431eaa) --- docs/slsa.md | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 docs/slsa.md diff --git a/docs/slsa.md b/docs/slsa.md new file mode 100644 index 000000000000..2374751deeda --- /dev/null +++ b/docs/slsa.md @@ -0,0 +1,338 @@ +# SLSA provenance + +BuildKit supports automatic creation of [SLSA Provenance](https://slsa.dev/provenance) +attestations to record the build process of the image, including details such +as: + +- Build timestamps +- Build parameters and environment +- Version control metadata +- Source code details +- Materials consumed during the build + +All provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) +in the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). + +When the final output format is a container image, provenance is attached +using the [attestation storage](./attestation-storage.md). + +To build an image with attached provenance use the `attest:provenance` option: + +```bash +buildctl build \ + --frontend=dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt attest:provenance= +``` + +You can also specify custom parameters for the provenance: + +```bash +buildctl build \ + --frontend=dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt attest:provenance=mode=min,inline-only=true +``` + +## Parameters + +| Parameter | Type | Default | Description | +| -------------- | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | +| `mode` | `min`,`max` | `max` | Configures the amount of provenance to be generated. See [mode](#mode) | +| `builder-id` | String | | Explicitly set SLSA [`builder.id`](https://slsa.dev/provenance/v0.2#builder.id) field | +| `reproducible` | `true`,`false` | `false` | Explicitly set SLSA [`metadata.reproducible`](https://slsa.dev/provenance/v0.2#metadata.reproducible) field | +| `inline-only` | `true`,`false` | `false` | Only embed provenance into exporters that support inline content. See [inline-only](#inline-only) | + +### `mode` + +Provenance can be generated in one of two `mode`s: `min` or `max`. By default, +when provenance is enabled, the `mode` parameter will be set to `max`. + +In `min` mode, BuildKit generate only the bare minimum amount of provenance, +including: + +- Build timestamps +- The frontend used +- The inputs used + +However, the values of build-arguments, the identities of secrets, and rich +layer metadata will not be included. `mode=min` should be safe to set on all +builds, as it does not leak information from any part of the build environment. + +In `max` mode, BuildKit generates all of the above, as well as: + +- The source Dockerfile, and rich layer metadata with sourcemaps to connect the + source with the build result +- The values of passed build arguments +- Metadata about secrets and ssh mounts + +Wherever possible, you should prefer `mode=max` as it contains signifantly +more detailed information for analysis. However, on some builds it may not be +appropriate, as it includes the values of various build arguments and metadata +about secrets - these builds should be refactored to prefer passing hidden +values through secrets wherever possible, to prevent unneccessary information +leakage. + +### `inline-only` + +When generated, provenance is by included in all exporters that support +attestations. The `inline-only` parameter allows configuring this behavior, to +only include the provenance results in exporters that support inline content, +specifically only exporters that produce container images. + +Since other exporters produce attestations into separate files, in their +filesystems, you may not want to include the provenance in these cases. + +## VCS + +Many BuildKit builds are started from the context of a version-controlled +source repository, such as git. If the build uses a remote git context, +BuildKit can extract the details automatically. However, if the build uses a +local context, if you want VCS detail included, you must include these details +manually. + +To include VCS details, you can set the additional frontend options +`vcs:source` and `vcs:revision`: + +```bash +buildctl build \ + --frontend=dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt attest:provenance= \ + --opt vcs:source=https://github.com/moby/buildkit \ + --opt vcs:revision=0c9b5aeb269c740650786ba77d882b0259415ec7 +``` + +These additional parameters will appear under the +`https://mobyproject.org/buildkit@v1#metadata` key in the provenance predicate. + +## Output + +To inspect the provenance that was generated, and attached to a container +image, you can use the `docker buildx imagetools` command to explore the +resulting image in your registry, following the format described in the +[attestation storage](./attestation-storage.md). + +For example, for a simple Docker image based on `alpine:latest`, we might get +the following provenance for a `mode=min` build: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "metadata": { + "buildInvocationID": "yirbp1aosi1vqjmi3z6bc75nb", + "buildStartedOn": "2022-12-08T11:48:59.466513707Z", + "buildFinishedOn": "2022-12-08T11:49:01.256820297Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": {} + } + } +} +``` + +For a similar build, but with `mode=max`: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "docker-image://docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + "platform": { + "Architecture": "amd64", + "OS": "linux" + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": [ + "step0:0" + ] + } + ] + }, + "metadata": { + "buildInvocationID": "46ue2x93k3xj5l463dektwldw", + "buildStartedOn": "2022-12-08T11:50:54.953375437Z", + "buildFinishedOn": "2022-12-08T11:50:55.447841328Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": { + "source": { + "locations": { + "step0": { + "locations": [ + { + "ranges": [ + { + "start": { + "line": 1 + }, + "end": { + "line": 1 + } + } + ] + } + ] + } + }, + "infos": [ + { + "filename": "Dockerfile", + "data": "RlJPTSBhbHBpbmU6bGF0ZXN0Cg==", + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "local://dockerfile", + "attrs": { + "local.differ": "none", + "local.followpaths": "[\"Dockerfile\",\"Dockerfile.dockerignore\",\"dockerfile\"]", + "local.session": "q2jnwdkas0i0iu4knchd92jaz", + "local.sharedkeyhint": "dockerfile" + } + } + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": [ + "step0:0" + ] + } + ] + } + ] + }, + "layers": { + "step0:0": [ + [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "digest": "sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad715", + "size": 3370706 + } + ] + ] + } + } + } + } +} +``` From a5053827fd9c201a11c755da81e44e5e70bf6837 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 19 Dec 2022 10:25:16 -0800 Subject: [PATCH 20/39] docs: update provenance docs Signed-off-by: Tonis Tiigi (cherry picked from commit 4d807dd9948aa3fed22b385d28a4c2e9c0bbea31) --- docs/slsa.md | 301 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 272 insertions(+), 29 deletions(-) diff --git a/docs/slsa.md b/docs/slsa.md index 2374751deeda..9f302c38ad08 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -1,22 +1,22 @@ # SLSA provenance BuildKit supports automatic creation of [SLSA Provenance](https://slsa.dev/provenance) -attestations to record the build process of the image, including details such -as: +attestations for the build process. Provenance attestations record information describing +how your build was performed and is important for tracking the security of your build pipeline. -- Build timestamps -- Build parameters and environment -- Version control metadata -- Source code details -- Materials consumed during the build +For example, provenance attestations created by BuildKit include details such as: + +- Build parameters and environment. +- Build timestamps. +- Version control metadata for your build sources. +- Build dependencies with their immutable checksums. For example, base images or external URLs used by the build. +- Descriptions of all build steps, with their source and layer mappings. All provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) in the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). -When the final output format is a container image, provenance is attached -using the [attestation storage](./attestation-storage.md). -To build an image with attached provenance use the `attest:provenance` option: +To build an image with attached provenance with `buildctl`, use the `attest:provenance` option: ```bash buildctl build \ @@ -36,6 +36,14 @@ buildctl build \ --opt attest:provenance=mode=min,inline-only=true ``` +Provenance attestations can be attached to build results from all BuildKit exporters. +When the final output format is a container image(`image` or `oci` exporter), provenance is attached +to the image using the [attestation storage](./attestation-storage.md) specification. When creating a +multi-platform image, every single-platform subimage will have its own provenance. + +When using the `local` or `tar` exporter, the provenance will be written to a file named `provenance.json` +and exported with your build result. + ## Parameters | Parameter | Type | Default | Description | @@ -47,17 +55,17 @@ buildctl build \ ### `mode` -Provenance can be generated in one of two `mode`s: `min` or `max`. By default, +Provenance can be generated in one of two modes: `min` or `max`. By default, when provenance is enabled, the `mode` parameter will be set to `max`. -In `min` mode, BuildKit generate only the bare minimum amount of provenance, +In `min` mode, BuildKit generates only the bare minimum amount of provenance, including: - Build timestamps - The frontend used -- The inputs used +- The build materials -However, the values of build-arguments, the identities of secrets, and rich +However, the values of build arguments, the identities of secrets, and rich layer metadata will not be included. `mode=min` should be safe to set on all builds, as it does not leak information from any part of the build environment. @@ -68,16 +76,16 @@ In `max` mode, BuildKit generates all of the above, as well as: - The values of passed build arguments - Metadata about secrets and ssh mounts -Wherever possible, you should prefer `mode=max` as it contains signifantly +Wherever possible, you should prefer `mode=max` as it contains significantly more detailed information for analysis. However, on some builds it may not be appropriate, as it includes the values of various build arguments and metadata about secrets - these builds should be refactored to prefer passing hidden -values through secrets wherever possible, to prevent unneccessary information +values through secrets wherever possible to prevent unnecessary information leakage. ### `inline-only` -When generated, provenance is by included in all exporters that support +By default, provenance is by included in all exporters that support attestations. The `inline-only` parameter allows configuring this behavior, to only include the provenance results in exporters that support inline content, specifically only exporters that produce container images. @@ -85,16 +93,254 @@ specifically only exporters that produce container images. Since other exporters produce attestations into separate files, in their filesystems, you may not want to include the provenance in these cases. -## VCS -Many BuildKit builds are started from the context of a version-controlled -source repository, such as git. If the build uses a remote git context, -BuildKit can extract the details automatically. However, if the build uses a -local context, if you want VCS detail included, you must include these details -manually. +## Definition + +The provenance format generated by BuildKit is defined by the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). + +The following is a selection of fields filled in by BuildKit: + +### `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) + +The `builder.id` field is set to the URL of the build, if available. This is value can be set with `builder-id` attestation parameter. + +``` + "builder": { + "id": "https://github.com/docker/buildx/actions/runs/3709599520" + }, +``` + +### `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) + +The `buildType` field is set to "https://mobyproject.org/buildkit@v1" can be used to determine the structure of the provenance content. + +``` + "buildType": "https://mobyproject.org/buildkit@v1", +``` + +### `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) + +Describes the config that initialized the build. + +``` + "invocation": { + "configSource": { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0-rc3", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + }, + "entryPoint": "Dockerfile" + }, +``` + +For builds that were initialized from a remote context like Git URL or HTTP URL, this structure +defines to URL to the context and its immutable digest. For frontend builds, the `entryPoint` field +defines the path for the Dockerfile that initialized the build (`filename` frontend option). + +### `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) + +Describes build inputs that were passed to the build. + +``` + "invocation": { + ... + "parameters": { + "frontend": "gateway.v0", + "args": { + "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR": "1", + "label:FOO": "bar", + "source": "docker/dockerfile-upstream:master", + "target": "release" + }, + "secrets": [ + { + "id": "GIT_AUTH_HEADER", + "optional": true + } + ... + ], + "ssh": [], + "locals": [], +``` + +- `frontend` defines BuildKit frontend used for the build. Currently, this can be `dockerfile.v0` or `gateway.v0`. +- `args` defines the build arguments passed to the BuildKit frontend. This maps to the `--opt` flag in `buildctl`. +The keys are specific to the frontend the build used. For example, in Dockerfile `build-arg:` and `label:` prefixes are +used for build arguments and labels, and `target` key defines the target stage that was built. +The `source` key defines the source image for the Gateway frontend, if used. +- `secrets` defines the secrets that were used to the build. Note that the actual secret values are not included. +- `ssh` defines the ssh forwards that were used to the build. +- `locals` defines if the build used any local sources. + + +### `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) + +``` + "environment": { + "platform": "linux/amd64" + } +``` + +The only value BuildKit currently sets is the `platform` of the current build machine. Note that this is not +necessarily the platform of the build result that can be determined from the `in-toto` subject field. + + +### `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) + +Defines all the external artifacts that were part of the build. These will be URLs to Git repositories +where the source code is coming from, HTTP URLs if you are building from a remote tarball or that you use +with `ADD` command in Dockerfile, or any Docker images that were used during the build. The URLs to +the Docker images will be in [Package URL](https://github.com/package-url/purl-spec) format. + +All the build materials will include the immutable checksum of the artifact. When building from a +mutable tag, you can use the digest information to determine if the artifact has been updated compared +to when the build ran. + +``` + "materials": [ + { + "uri": "pkg:docker/alpine@3.17?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + } + } + ... + ], +``` + +### `buildConfig` [(SLSA)](https://slsa.dev/provenance/v0.2#buildConfig) + +Describes all the build steps performed during the build. + +BuildKit internally uses LLB definition to execute the build steps. The LLB definition of the build steps +is defined in `buildConfig.llbDefinition` field. + +Each LLB step is the JSON definition of the [LLB ProtoBuf API](https://github.com/moby/buildkit/blob/v0.10.0/solver/pb/ops.proto). +The dependencies for a vertex in the LLB graph can be found in the `inputs` field for every step. + +``` + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "exec": { + "meta": { + "args": [ + "/bin/sh", + "-c", + "go build ." + ], + "env": [ + "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "GOPATH=/go", + "GOFLAGS=-mod=vendor", + ], + "cwd": "/src", + }, + "mounts": [...] + } + }, + "platform": {...}, + }, + "inputs": [ + "step8:0", + "step2:0", + ] + }, + ... +``` + +### `metadata.buildIncocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) + +Unique identifier for the build invocation. Then building a multi-platform image with a single build request, +this value will be the same for all the single-platform subimages. + +``` + "metadata": { + "buildInvocationID": "rpv7a389uzil5lqmrgwhijwjz", +``` + +### `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) + +Timestamp when the build started. -To include VCS details, you can set the additional frontend options -`vcs:source` and `vcs:revision`: +``` + "buildStartedOn": "2021-11-17T15:00:00Z", +``` + +### `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) + +Timestamp when the build finished. + +``` + "buildFinishedOn": "2021-11-17T15:01:00Z", +``` + +### `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) + +Defines if the provenance information is complete. + +`completeness.parameters` is true if all the build arguments are included in the `invocation.parameters` field. +When building with `min` mode, the build arguments are not included in the provenance information and parameters +are not complete. Parameters are also not complete on direct LLB builds that did not use a frontend. + +`completeness.environment` is always true for BuildKit builds. + +`completeness.materials` is true if `materials` field includes all the dependencies of the build. When building +from untracked source in a local directory, the materials are not complete, while when building from a remote +Git repository all materials can be tracked by BuildKit and `completeness.materials` is true. + +`https://mobyproject.org/buildkit@v1#hermetic` is a custom field that is true if the build was hermetic and +did not access the network. In Dockerfiles, a build is hermetic if it does not use `RUN` commands or disables +network with `--network=none` flag. + +``` + "completeness": { + "parameters": true, + "environment": true, + "materials": true, + "https://mobyproject.org/buildkit@v1#hermetic": true + } + } +``` + +### `metadata.reproducible` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.reproducible) + +Defines if the build result is supposed to be byte-by-byte reproducible. This value can be set by the +user with the `reproducible=true` attestation parameter. + +``` + "reproducible": false +``` + +### `metadata.https://mobyproject.org/buildkit@v1#metadata` + +This extension field defines BuildKit-specific additional metadata that is not part of the SLSA provenance spec. + +`source` defines the source mapping of LLB build steps defined in `buildConfig.llbDefinition` to their +original source code (for example Dockerfile commands). `source.locations` contains the ranges of all the +Dockerfile commands that were executed by a LLB step. `source.infos` array contains the source code itself. +This mapping is present if the BuildKit frontend provided it when creating the LLB definition. + +`layers` defines the layer mapping of LLB build step mounts defined in `buildConfig.llbDefinition` to the +OCI descriptors of equivalent layers. This mapping is present if the layer data was available, usually +when attestation is for an image or if the build step pulled in image data as part of the build. + +`vcs` defines the optional metadata for the version control system that was used for the build. If a build +uses a remote context from Git repository, BuildKit can extract the details of the version control system +automatically and present it in the `invocation.configSource` field. But if the build uses a source from a +local directory, the VCS information is lost even if the directory contained a Git repository. In this case, +the build client can send additional `vcs:source` and `vcs:revision` build options and BuildKit will add +them to the provenance attestations as extra metadata. Note that contrary to the `invocation.configSource` +field, these values were not verified by BuildKit and should not be trusted other than for a metadata hint. ```bash buildctl build \ @@ -106,12 +352,9 @@ buildctl build \ --opt vcs:revision=0c9b5aeb269c740650786ba77d882b0259415ec7 ``` -These additional parameters will appear under the -`https://mobyproject.org/buildkit@v1#metadata` key in the provenance predicate. - ## Output -To inspect the provenance that was generated, and attached to a container +To inspect the provenance that was generated and attached to a container image, you can use the `docker buildx imagetools` command to explore the resulting image in your registry, following the format described in the [attestation storage](./attestation-storage.md). From d605e0ebc9de272095461875541edfff1058ed68 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 20 Dec 2022 10:13:14 -0800 Subject: [PATCH 21/39] docs: update hermetic field after it was moved in implementation Signed-off-by: Tonis Tiigi (cherry picked from commit c1963ec9df1cea17d19e90acdb03396a79005898) --- docs/slsa.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/slsa.md b/docs/slsa.md index 9f302c38ad08..3078baf3cb56 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -298,16 +298,12 @@ are not complete. Parameters are also not complete on direct LLB builds that did from untracked source in a local directory, the materials are not complete, while when building from a remote Git repository all materials can be tracked by BuildKit and `completeness.materials` is true. -`https://mobyproject.org/buildkit@v1#hermetic` is a custom field that is true if the build was hermetic and -did not access the network. In Dockerfiles, a build is hermetic if it does not use `RUN` commands or disables -network with `--network=none` flag. ``` "completeness": { "parameters": true, "environment": true, - "materials": true, - "https://mobyproject.org/buildkit@v1#hermetic": true + "materials": true } } ``` @@ -321,6 +317,19 @@ user with the `reproducible=true` attestation parameter. "reproducible": false ``` + +### `metadata.https://mobyproject.org/buildkit@v1#hermetic` + +This extension field is set to true if the build was hermetic and did not access the network. +In Dockerfiles, a build is hermetic if it does not use `RUN` commands or disables +network with `--network=none` flag. + +``` + "metadata": { + "https://mobyproject.org/buildkit@v1#hermetic": true, + ... +``` + ### `metadata.https://mobyproject.org/buildkit@v1#metadata` This extension field defines BuildKit-specific additional metadata that is not part of the SLSA provenance spec. From fba361049a533c900fa1b424ff0770eb14021c5f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 20 Dec 2022 10:33:18 -0800 Subject: [PATCH 22/39] docs: add filename to provenance attestation Signed-off-by: Tonis Tiigi (cherry picked from commit 9c212c25bd8833d8ca799889e07a952198634f27) --- docs/slsa.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/slsa.md b/docs/slsa.md index 3078baf3cb56..f60632432fa3 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -46,12 +46,13 @@ and exported with your build result. ## Parameters -| Parameter | Type | Default | Description | +| Parameter | Type | Default | Description | | -------------- | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | -| `mode` | `min`,`max` | `max` | Configures the amount of provenance to be generated. See [mode](#mode) | -| `builder-id` | String | | Explicitly set SLSA [`builder.id`](https://slsa.dev/provenance/v0.2#builder.id) field | -| `reproducible` | `true`,`false` | `false` | Explicitly set SLSA [`metadata.reproducible`](https://slsa.dev/provenance/v0.2#metadata.reproducible) field | -| `inline-only` | `true`,`false` | `false` | Only embed provenance into exporters that support inline content. See [inline-only](#inline-only) | +| `mode` | `min`,`max` | `max` | Configures the amount of provenance to be generated. See [mode](#mode) | +| `builder-id` | String | | Explicitly set SLSA [`builder.id`](https://slsa.dev/provenance/v0.2#builder.id) field | +| `filename` | String | `provenance.json` | Set filename for provenance attestation when exported with `local` or `tar` exporter | +| `reproducible` | `true`,`false` | `false` | Explicitly set SLSA [`metadata.reproducible`](https://slsa.dev/provenance/v0.2#metadata.reproducible) field | +| `inline-only` | `true`,`false` | `false` | Only embed provenance into exporters that support inline content. See [inline-only](#inline-only) | ### `mode` From d0300e564cf5a5687637f5b34503eb612ec28ad4 Mon Sep 17 00:00:00 2001 From: David Karlsson Date: Thu, 22 Dec 2022 09:53:57 +0100 Subject: [PATCH 23/39] docs: slsa editorial fixes Signed-off-by: David Karlsson (cherry picked from commit 066091a5006f0baef1b35cc5673aeaf685e377f1) --- docs/slsa.md | 148 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 47 deletions(-) diff --git a/docs/slsa.md b/docs/slsa.md index f60632432fa3..5f366ff27128 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -2,9 +2,10 @@ BuildKit supports automatic creation of [SLSA Provenance](https://slsa.dev/provenance) attestations for the build process. Provenance attestations record information describing -how your build was performed and is important for tracking the security of your build pipeline. +how a build was created, and is important for tracking the security of your software +artifacts. -For example, provenance attestations created by BuildKit include details such as: +Provenance attestations created by BuildKit include details such as: - Build parameters and environment. - Build timestamps. @@ -12,11 +13,12 @@ For example, provenance attestations created by BuildKit include details such as - Build dependencies with their immutable checksums. For example, base images or external URLs used by the build. - Descriptions of all build steps, with their source and layer mappings. -All provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) +Provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) in the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). +## Build with provenance attestations -To build an image with attached provenance with `buildctl`, use the `attest:provenance` option: +To build an image with provenance attestations using `buildctl`, use the `attest:provenance` option: ```bash buildctl build \ @@ -26,7 +28,7 @@ buildctl build \ --opt attest:provenance= ``` -You can also specify custom parameters for the provenance: +You can also customize the attestations using parameters: ```bash buildctl build \ @@ -36,13 +38,13 @@ buildctl build \ --opt attest:provenance=mode=min,inline-only=true ``` -Provenance attestations can be attached to build results from all BuildKit exporters. -When the final output format is a container image(`image` or `oci` exporter), provenance is attached -to the image using the [attestation storage](./attestation-storage.md) specification. When creating a -multi-platform image, every single-platform subimage will have its own provenance. +All BuildKit exporters support attaching attestations to build results. +When the final output format is a container image (`image` or `oci` exporter), provenance is attached +to the image using the format described in the [attestation storage specification](./attestation-storage.md). +When creating a multi-platform image, each platform version of the image gets its own provenance. -When using the `local` or `tar` exporter, the provenance will be written to a file named `provenance.json` -and exported with your build result. +If you use the `local` or `tar` exporter, the provenance will be written to a file named `provenance.json` +and exported with your build result, in the root directory. ## Parameters @@ -99,11 +101,14 @@ filesystems, you may not want to include the provenance in these cases. The provenance format generated by BuildKit is defined by the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). -The following is a selection of fields filled in by BuildKit: +This section describes how BuildKit populate each field, and whether the +field gets included when you generate attestations `mode=min` and `mode=max`. ### `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) -The `builder.id` field is set to the URL of the build, if available. This is value can be set with `builder-id` attestation parameter. +Included with `mode=min`. + +The `builder.id` field is set to the URL of the build, if available. ``` "builder": { @@ -111,8 +116,12 @@ The `builder.id` field is set to the URL of the build, if available. This is val }, ``` +This value can be set using the `builder-id` attestation parameter. + ### `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) +Included with `mode=min`. + The `buildType` field is set to "https://mobyproject.org/buildkit@v1" can be used to determine the structure of the provenance content. ``` @@ -121,6 +130,8 @@ The `buildType` field is set to "https://mobyproject.org/buildkit@v1" can be use ### `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) +Included with `mode=min`. + Describes the config that initialized the build. ``` @@ -134,12 +145,15 @@ Describes the config that initialized the build. }, ``` -For builds that were initialized from a remote context like Git URL or HTTP URL, this structure -defines to URL to the context and its immutable digest. For frontend builds, the `entryPoint` field -defines the path for the Dockerfile that initialized the build (`filename` frontend option). +For builds that were initialized from a remote context, like a Git or HTTP URL, this object +defines to URL to the context and its immutable digest. For builds using a local frontend, +such as a Dockerfile, the `entryPoint` field defines the path for the frontend file that +initialized the build (`filename` frontend option). ### `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) +Partially included with `mode=min`. + Describes build inputs that were passed to the build. ``` @@ -164,18 +178,26 @@ Describes build inputs that were passed to the build. "locals": [], ``` -- `frontend` defines BuildKit frontend used for the build. Currently, this can be `dockerfile.v0` or `gateway.v0`. +The following properties are included with both `mode=min` and `mode=max`: + +- `locals` lists any local sources used in the build, including the build context and frontend file. +- `frontend` defines type of BuildKit frontend used for the build. Currently, this can be `dockerfile.v0` or `gateway.v0`. - `args` defines the build arguments passed to the BuildKit frontend. This maps to the `--opt` flag in `buildctl`. -The keys are specific to the frontend the build used. For example, in Dockerfile `build-arg:` and `label:` prefixes are -used for build arguments and labels, and `target` key defines the target stage that was built. -The `source` key defines the source image for the Gateway frontend, if used. -- `secrets` defines the secrets that were used to the build. Note that the actual secret values are not included. + + The keys are specific to the frontend the build used. For example, in Dockerfile `build-arg:` and `label:` prefixes + are used for build arguments and labels, and `target` key defines the target stage that was built. The `source` key + defines the source image for the Gateway frontend, if used. + +The following properties are only included with `mode=max`: + +- `secrets` defines what secrets were used to the build. Note that actual secret values are not included. - `ssh` defines the ssh forwards that were used to the build. -- `locals` defines if the build used any local sources. ### `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) +Included with `mode=min`. + ``` "environment": { "platform": "linux/amd64" @@ -188,10 +210,15 @@ necessarily the platform of the build result that can be determined from the `in ### `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) -Defines all the external artifacts that were part of the build. These will be URLs to Git repositories -where the source code is coming from, HTTP URLs if you are building from a remote tarball or that you use -with `ADD` command in Dockerfile, or any Docker images that were used during the build. The URLs to -the Docker images will be in [Package URL](https://github.com/package-url/purl-spec) format. +Included with `mode=min`. + +Defines all the external artifacts that were part of the build. The value depends on the type of artifact: + +- The URL of Git repositories containing source code for the image +- HTTP URLs if you are building from a remote tarball, or that was included using an `ADD` command in Dockerfile +- Any Docker images that were used during the build + +The URLs to the Docker images will be in [Package URL](https://github.com/package-url/purl-spec) format. All the build materials will include the immutable checksum of the artifact. When building from a mutable tag, you can use the digest information to determine if the artifact has been updated compared @@ -217,7 +244,9 @@ to when the build ran. ### `buildConfig` [(SLSA)](https://slsa.dev/provenance/v0.2#buildConfig) -Describes all the build steps performed during the build. +Only included with `mode=max`. + +Defines the build steps performed during the build. BuildKit internally uses LLB definition to execute the build steps. The LLB definition of the build steps is defined in `buildConfig.llbDefinition` field. @@ -259,10 +288,12 @@ The dependencies for a vertex in the LLB graph can be found in the `inputs` fiel ... ``` -### `metadata.buildIncocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) +### `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) + +Included with `mode=min`. -Unique identifier for the build invocation. Then building a multi-platform image with a single build request, -this value will be the same for all the single-platform subimages. +Unique identifier for the build invocation. When building a multi-platform image with a single build request, +this value will be the shared by all the platform versions of the image. ``` "metadata": { @@ -271,6 +302,8 @@ this value will be the same for all the single-platform subimages. ### `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) +Included with `mode=min`. + Timestamp when the build started. ``` @@ -279,6 +312,8 @@ Timestamp when the build started. ### `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) +Included with `mode=min`. + Timestamp when the build finished. ``` @@ -287,6 +322,8 @@ Timestamp when the build finished. ### `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) +Included with `mode=min`. + Defines if the provenance information is complete. `completeness.parameters` is true if all the build arguments are included in the `invocation.parameters` field. @@ -321,6 +358,8 @@ user with the `reproducible=true` attestation parameter. ### `metadata.https://mobyproject.org/buildkit@v1#hermetic` +Included with `mode=min`. + This extension field is set to true if the build was hermetic and did not access the network. In Dockerfiles, a build is hermetic if it does not use `RUN` commands or disables network with `--network=none` flag. @@ -333,24 +372,39 @@ network with `--network=none` flag. ### `metadata.https://mobyproject.org/buildkit@v1#metadata` +Partially included with `mode=min`. + This extension field defines BuildKit-specific additional metadata that is not part of the SLSA provenance spec. -`source` defines the source mapping of LLB build steps defined in `buildConfig.llbDefinition` to their -original source code (for example Dockerfile commands). `source.locations` contains the ranges of all the -Dockerfile commands that were executed by a LLB step. `source.infos` array contains the source code itself. -This mapping is present if the BuildKit frontend provided it when creating the LLB definition. +#### `source` -`layers` defines the layer mapping of LLB build step mounts defined in `buildConfig.llbDefinition` to the +Only included with `mode=max`. + +Defines a source mapping of LLB build steps, defined in the `buildConfig.llbDefinition` field, to their +original source code (for example, Dockerfile commands). The `source.locations` field contains the ranges +of all the Dockerfile commands that were executed in an LLB step. `source.infos` array contains the source +code itself. This mapping is present if the BuildKit frontend provided it when creating the LLB definition. + +#### `layers` + +Only included with `mode=max`. + +Defines the layer mapping of LLB build step mounts defined in `buildConfig.llbDefinition` to the OCI descriptors of equivalent layers. This mapping is present if the layer data was available, usually when attestation is for an image or if the build step pulled in image data as part of the build. -`vcs` defines the optional metadata for the version control system that was used for the build. If a build -uses a remote context from Git repository, BuildKit can extract the details of the version control system -automatically and present it in the `invocation.configSource` field. But if the build uses a source from a -local directory, the VCS information is lost even if the directory contained a Git repository. In this case, -the build client can send additional `vcs:source` and `vcs:revision` build options and BuildKit will add -them to the provenance attestations as extra metadata. Note that contrary to the `invocation.configSource` -field, these values were not verified by BuildKit and should not be trusted other than for a metadata hint. +#### `vcs` + +Included with both `mode=min` and `mode=max`. + +Defines optional metadata for the version control system used for the build. If a build uses a +remote context from Git repository, BuildKit extracts the details of the version control +system automatically and displays it in the `invocation.configSource` field. But if the build +uses a source from a local directory, the VCS information is lost even if the directory contained +a Git repository. In this case, the build client can send additional `vcs:source` and +`vcs:revision` build options and BuildKit will add them to the provenance attestations as extra +metadata. Note that, contrary to the `invocation.configSource` field, BuildKit doesn't verify the +`vcs` values, and as such they can't be trusted and should only be used as a metadata hint. ```bash buildctl build \ @@ -365,12 +419,12 @@ buildctl build \ ## Output To inspect the provenance that was generated and attached to a container -image, you can use the `docker buildx imagetools` command to explore the -resulting image in your registry, following the format described in the -[attestation storage](./attestation-storage.md). +image, you can use the `docker buildx imagetools` command to inspect the +image in a registry. Inspecting the attestation displays the format described +in the [attestation storage specification](./attestation-storage.md). -For example, for a simple Docker image based on `alpine:latest`, we might get -the following provenance for a `mode=min` build: +For example, inspecting a simple Docker image based on `alpine:latest` results +in a provenance attestation similar to the following, for a `mode=min` build: ```json { From dd51701a770d11f1e21e3f5ebb149933937f6b8f Mon Sep 17 00:00:00 2001 From: David Karlsson Date: Thu, 22 Dec 2022 11:08:20 +0100 Subject: [PATCH 24/39] docs: moved slsa definitions to a separate page Signed-off-by: David Karlsson (cherry picked from commit ea715c8fcadeb4525ab067545fa6fa66fa3e8f8b) --- docs/slsa-definitions.md | 552 +++++++++++++++++++++++++++++++++++++++ docs/slsa.md | 549 +------------------------------------- 2 files changed, 554 insertions(+), 547 deletions(-) create mode 100644 docs/slsa-definitions.md diff --git a/docs/slsa-definitions.md b/docs/slsa-definitions.md new file mode 100644 index 000000000000..b0b667fb190e --- /dev/null +++ b/docs/slsa-definitions.md @@ -0,0 +1,552 @@ +# SLSA definitions + +The provenance format generated by BuildKit is defined by the +[SLSA Provenance format](https://slsa.dev/provenance/v0.2). + +This page describes how BuildKit populate each field, and whether the field gets +included when you generate attestations `mode=min` and `mode=max`. + +## `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) + +Included with `mode=min`. + +The `builder.id` field is set to the URL of the build, if available. + +```json + "builder": { + "id": "https://github.com/docker/buildx/actions/runs/3709599520" + }, +``` + +This value can be set using the `builder-id` attestation parameter. + +## `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) + +Included with `mode=min`. + +The `buildType` field is set to `https://mobyproject.org/buildkit@v1` can be +used to determine the structure of the provenance content. + +``` + "buildType": "https://mobyproject.org/buildkit@v1", +``` + +## `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) + +Included with `mode=min`. + +Describes the config that initialized the build. + +```json + "invocation": { + "configSource": { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0-rc3", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + }, + "entryPoint": "Dockerfile" + }, +``` + +For builds initialized from a remote context, like a Git or HTTP URL, this +object defines to URL to the context and its immutable digest. For builds using +a local frontend, such as a Dockerfile, the `entryPoint` field defines the path +for the frontend file that initialized the build (`filename` frontend option). + +## `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) + +Partially included with `mode=min`. + +Describes build inputs passed to the build. + +```json + "invocation": { + ... + "parameters": { + "frontend": "gateway.v0", + "args": { + "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR": "1", + "label:FOO": "bar", + "source": "docker/dockerfile-upstream:master", + "target": "release" + }, + "secrets": [ + { + "id": "GIT_AUTH_HEADER", + "optional": true + } + ... + ], + "ssh": [], + "locals": [], +``` + +The following fields are included with both `mode=min` and `mode=max`: + +- `locals` lists any local sources used in the build, including the build + context and frontend file. +- `frontend` defines type of BuildKit frontend used for the build. Currently, + this can be `dockerfile.v0` or `gateway.v0`. +- `args` defines the build arguments passed to the BuildKit frontend. + + The keys inside the `args` object reflect the options as BuildKit receives + them. For example, `build-arg` and `label` prefixes are used for build + arguments and labels, and `target` key defines the target stage that was + built. The `source` key defines the source image for the Gateway frontend, if + used. + +The following fields are only included with `mode=max`: + +- `secrets` defines secrets used during the build. Note that actual secret + values are not included. +- `ssh` defines the ssh forwards used during the build. + +## `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) + +Included with `mode=min`. + +```json + "environment": { + "platform": "linux/amd64" + } +``` + +The only value BuildKit currently sets is the `platform` of the current build +machine. Note that this is not necessarily the platform of the build result that +can be determined from the `in-toto` subject field. + +## `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) + +Included with `mode=min`. + +Defines all the external artifacts that were part of the build. The value +depends on the type of artifact: + +- The URL of Git repositories containing source code for the image +- HTTP URLs if you are building from a remote tarball, or that was included + using an `ADD` command in Dockerfile +- Any Docker images used during the build + +The URLs to the Docker images will be in +[Package URL](https://github.com/package-url/purl-spec) format. + +All the build materials will include the immutable checksum of the artifact. +When building from a mutable tag, you can use the digest information to +determine if the artifact has been updated compared to when the build ran. + +```json + "materials": [ + { + "uri": "pkg:docker/alpine@3.17?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + } + } + ... + ], +``` + +## `buildConfig` [(SLSA)](https://slsa.dev/provenance/v0.2#buildConfig) + +Only included with `mode=max`. + +Defines the build steps performed during the build. + +BuildKit internally uses LLB definition to execute the build steps. The LLB +definition of the build steps is defined in `buildConfig.llbDefinition` field. + +Each LLB step is the JSON definition of the +[LLB ProtoBuf API](https://github.com/moby/buildkit/blob/v0.10.0/solver/pb/ops.proto). +The dependencies for a vertex in the LLB graph can be found in the `inputs` +field for every step. + +```json + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "exec": { + "meta": { + "args": [ + "/bin/sh", + "-c", + "go build ." + ], + "env": [ + "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "GOPATH=/go", + "GOFLAGS=-mod=vendor", + ], + "cwd": "/src", + }, + "mounts": [...] + } + }, + "platform": {...}, + }, + "inputs": [ + "step8:0", + "step2:0", + ] + }, + ... +``` + +## `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) + +Included with `mode=min`. + +Unique identifier for the build invocation. When building a multi-platform image +with a single build request, this value will be the shared by all the platform +versions of the image. + +```json + "metadata": { + "buildInvocationID": "rpv7a389uzil5lqmrgwhijwjz", +``` + +## `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) + +Included with `mode=min`. + +Timestamp when the build started. + +```json + "buildStartedOn": "2021-11-17T15:00:00Z", +``` + +## `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) + +Included with `mode=min`. + +Timestamp when the build finished. + +```json + "buildFinishedOn": "2021-11-17T15:01:00Z", +``` + +## `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) + +Included with `mode=min`. + +Defines if the provenance information is complete. + +`completeness.parameters` is true if all the build arguments are included in the +`invocation.parameters` field. When building with `min` mode, the build +arguments are not included in the provenance information and parameters are not +complete. Parameters are also not complete on direct LLB builds that did not use +a frontend. + +`completeness.environment` is always true for BuildKit builds. + +`completeness.materials` is true if `materials` field includes all the +dependencies of the build. When building from un-tracked source in a local +directory, the materials are not complete, while when building from a remote Git +repository all materials can be tracked by BuildKit and `completeness.materials` +is true. + +```json + "completeness": { + "parameters": true, + "environment": true, + "materials": true + } + } +``` + +## `metadata.reproducible` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.reproducible) + +Defines if the build result is supposed to be byte-by-byte reproducible. This +value can be set by the user with the `reproducible=true` attestation parameter. + +```json + "reproducible": false +``` + +## `metadata.https://mobyproject.org/buildkit@v1#hermetic` + +Included with `mode=min`. + +This extension field is set to true if the build was hermetic and did not access +the network. In Dockerfiles, a build is hermetic if it does not use `RUN` +commands or disables network with `--network=none` flag. + +```json + "metadata": { + "https://mobyproject.org/buildkit@v1#hermetic": true, + ... +``` + +## `metadata.https://mobyproject.org/buildkit@v1#metadata` + +Partially included with `mode=min`. + +This extension field defines BuildKit-specific additional metadata that is not +part of the SLSA provenance spec. + +### `source` + +Only included with `mode=max`. + +Defines a source mapping of LLB build steps, defined in the +`buildConfig.llbDefinition` field, to their original source code (for example, +Dockerfile commands). The `source.locations` field contains the ranges of all +the Dockerfile commands ran in an LLB step. `source.infos` array contains the +source code itself. This mapping is present if the BuildKit frontend provided it +when creating the LLB definition. + +### `layers` + +Only included with `mode=max`. + +Defines the layer mapping of LLB build step mounts defined in +`buildConfig.llbDefinition` to the OCI descriptors of equivalent layers. This +mapping is present if the layer data was available, usually when attestation is +for an image or if the build step pulled in image data as part of the build. + +### `vcs` + +Included with both `mode=min` and `mode=max`. + +Defines optional metadata for the version control system used for the build. If +a build uses a remote context from Git repository, BuildKit extracts the details +of the version control system automatically and displays it in the +`invocation.configSource` field. But if the build uses a source from a local +directory, the VCS information is lost even if the directory contained a Git +repository. In this case, the build client can send additional `vcs:source` and +`vcs:revision` build options and BuildKit will add them to the provenance +attestations as extra metadata. Note that, contrary to the +`invocation.configSource` field, BuildKit doesn't verify the `vcs` values, and +as such they can't be trusted and should only be used as a metadata hint. + +## Output + +To inspect the provenance that was generated and attached to a container image, +you can use the `docker buildx imagetools` command to inspect the image in a +registry. Inspecting the attestation displays the format described in the +[attestation storage specification](./attestation-storage.md). + +For example, inspecting a simple Docker image based on `alpine:latest` results +in a provenance attestation similar to the following, for a `mode=min` build: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "metadata": { + "buildInvocationID": "yirbp1aosi1vqjmi3z6bc75nb", + "buildStartedOn": "2022-12-08T11:48:59.466513707Z", + "buildFinishedOn": "2022-12-08T11:49:01.256820297Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": {} + } + } +} +``` + +For a similar build, but with `mode=max`: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "docker-image://docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + "platform": { + "Architecture": "amd64", + "OS": "linux" + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": ["step0:0"] + } + ] + }, + "metadata": { + "buildInvocationID": "46ue2x93k3xj5l463dektwldw", + "buildStartedOn": "2022-12-08T11:50:54.953375437Z", + "buildFinishedOn": "2022-12-08T11:50:55.447841328Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": { + "source": { + "locations": { + "step0": { + "locations": [ + { + "ranges": [ + { + "start": { + "line": 1 + }, + "end": { + "line": 1 + } + } + ] + } + ] + } + }, + "infos": [ + { + "filename": "Dockerfile", + "data": "RlJPTSBhbHBpbmU6bGF0ZXN0Cg==", + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "local://dockerfile", + "attrs": { + "local.differ": "none", + "local.followpaths": "[\"Dockerfile\",\"Dockerfile.dockerignore\",\"dockerfile\"]", + "local.session": "q2jnwdkas0i0iu4knchd92jaz", + "local.sharedkeyhint": "dockerfile" + } + } + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": ["step0:0"] + } + ] + } + ] + }, + "layers": { + "step0:0": [ + [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "digest": "sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad715", + "size": 3370706 + } + ] + ] + } + } + } + } +} +``` diff --git a/docs/slsa.md b/docs/slsa.md index 5f366ff27128..cab9d94db1b2 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -16,6 +16,8 @@ Provenance attestations created by BuildKit include details such as: Provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) in the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). +For more information about how the attestation fields get generated, see [SLSA definitions](./slsa-definitions.md). + ## Build with provenance attestations To build an image with provenance attestations using `buildctl`, use the `attest:provenance` option: @@ -96,550 +98,3 @@ specifically only exporters that produce container images. Since other exporters produce attestations into separate files, in their filesystems, you may not want to include the provenance in these cases. - -## Definition - -The provenance format generated by BuildKit is defined by the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). - -This section describes how BuildKit populate each field, and whether the -field gets included when you generate attestations `mode=min` and `mode=max`. - -### `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) - -Included with `mode=min`. - -The `builder.id` field is set to the URL of the build, if available. - -``` - "builder": { - "id": "https://github.com/docker/buildx/actions/runs/3709599520" - }, -``` - -This value can be set using the `builder-id` attestation parameter. - -### `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) - -Included with `mode=min`. - -The `buildType` field is set to "https://mobyproject.org/buildkit@v1" can be used to determine the structure of the provenance content. - -``` - "buildType": "https://mobyproject.org/buildkit@v1", -``` - -### `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) - -Included with `mode=min`. - -Describes the config that initialized the build. - -``` - "invocation": { - "configSource": { - "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0-rc3", - "digest": { - "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" - }, - "entryPoint": "Dockerfile" - }, -``` - -For builds that were initialized from a remote context, like a Git or HTTP URL, this object -defines to URL to the context and its immutable digest. For builds using a local frontend, -such as a Dockerfile, the `entryPoint` field defines the path for the frontend file that -initialized the build (`filename` frontend option). - -### `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) - -Partially included with `mode=min`. - -Describes build inputs that were passed to the build. - -``` - "invocation": { - ... - "parameters": { - "frontend": "gateway.v0", - "args": { - "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR": "1", - "label:FOO": "bar", - "source": "docker/dockerfile-upstream:master", - "target": "release" - }, - "secrets": [ - { - "id": "GIT_AUTH_HEADER", - "optional": true - } - ... - ], - "ssh": [], - "locals": [], -``` - -The following properties are included with both `mode=min` and `mode=max`: - -- `locals` lists any local sources used in the build, including the build context and frontend file. -- `frontend` defines type of BuildKit frontend used for the build. Currently, this can be `dockerfile.v0` or `gateway.v0`. -- `args` defines the build arguments passed to the BuildKit frontend. This maps to the `--opt` flag in `buildctl`. - - The keys are specific to the frontend the build used. For example, in Dockerfile `build-arg:` and `label:` prefixes - are used for build arguments and labels, and `target` key defines the target stage that was built. The `source` key - defines the source image for the Gateway frontend, if used. - -The following properties are only included with `mode=max`: - -- `secrets` defines what secrets were used to the build. Note that actual secret values are not included. -- `ssh` defines the ssh forwards that were used to the build. - - -### `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) - -Included with `mode=min`. - -``` - "environment": { - "platform": "linux/amd64" - } -``` - -The only value BuildKit currently sets is the `platform` of the current build machine. Note that this is not -necessarily the platform of the build result that can be determined from the `in-toto` subject field. - - -### `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) - -Included with `mode=min`. - -Defines all the external artifacts that were part of the build. The value depends on the type of artifact: - -- The URL of Git repositories containing source code for the image -- HTTP URLs if you are building from a remote tarball, or that was included using an `ADD` command in Dockerfile -- Any Docker images that were used during the build - -The URLs to the Docker images will be in [Package URL](https://github.com/package-url/purl-spec) format. - -All the build materials will include the immutable checksum of the artifact. When building from a -mutable tag, you can use the digest information to determine if the artifact has been updated compared -to when the build ran. - -``` - "materials": [ - { - "uri": "pkg:docker/alpine@3.17?platform=linux%2Famd64", - "digest": { - "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" - } - }, - { - "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0", - "digest": { - "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" - } - } - ... - ], -``` - -### `buildConfig` [(SLSA)](https://slsa.dev/provenance/v0.2#buildConfig) - -Only included with `mode=max`. - -Defines the build steps performed during the build. - -BuildKit internally uses LLB definition to execute the build steps. The LLB definition of the build steps -is defined in `buildConfig.llbDefinition` field. - -Each LLB step is the JSON definition of the [LLB ProtoBuf API](https://github.com/moby/buildkit/blob/v0.10.0/solver/pb/ops.proto). -The dependencies for a vertex in the LLB graph can be found in the `inputs` field for every step. - -``` - "buildConfig": { - "llbDefinition": [ - { - "id": "step0", - "op": { - "Op": { - "exec": { - "meta": { - "args": [ - "/bin/sh", - "-c", - "go build ." - ], - "env": [ - "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "GOPATH=/go", - "GOFLAGS=-mod=vendor", - ], - "cwd": "/src", - }, - "mounts": [...] - } - }, - "platform": {...}, - }, - "inputs": [ - "step8:0", - "step2:0", - ] - }, - ... -``` - -### `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) - -Included with `mode=min`. - -Unique identifier for the build invocation. When building a multi-platform image with a single build request, -this value will be the shared by all the platform versions of the image. - -``` - "metadata": { - "buildInvocationID": "rpv7a389uzil5lqmrgwhijwjz", -``` - -### `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) - -Included with `mode=min`. - -Timestamp when the build started. - -``` - "buildStartedOn": "2021-11-17T15:00:00Z", -``` - -### `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) - -Included with `mode=min`. - -Timestamp when the build finished. - -``` - "buildFinishedOn": "2021-11-17T15:01:00Z", -``` - -### `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) - -Included with `mode=min`. - -Defines if the provenance information is complete. - -`completeness.parameters` is true if all the build arguments are included in the `invocation.parameters` field. -When building with `min` mode, the build arguments are not included in the provenance information and parameters -are not complete. Parameters are also not complete on direct LLB builds that did not use a frontend. - -`completeness.environment` is always true for BuildKit builds. - -`completeness.materials` is true if `materials` field includes all the dependencies of the build. When building -from untracked source in a local directory, the materials are not complete, while when building from a remote -Git repository all materials can be tracked by BuildKit and `completeness.materials` is true. - - -``` - "completeness": { - "parameters": true, - "environment": true, - "materials": true - } - } -``` - -### `metadata.reproducible` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.reproducible) - -Defines if the build result is supposed to be byte-by-byte reproducible. This value can be set by the -user with the `reproducible=true` attestation parameter. - -``` - "reproducible": false -``` - - -### `metadata.https://mobyproject.org/buildkit@v1#hermetic` - -Included with `mode=min`. - -This extension field is set to true if the build was hermetic and did not access the network. -In Dockerfiles, a build is hermetic if it does not use `RUN` commands or disables -network with `--network=none` flag. - -``` - "metadata": { - "https://mobyproject.org/buildkit@v1#hermetic": true, - ... -``` - -### `metadata.https://mobyproject.org/buildkit@v1#metadata` - -Partially included with `mode=min`. - -This extension field defines BuildKit-specific additional metadata that is not part of the SLSA provenance spec. - -#### `source` - -Only included with `mode=max`. - -Defines a source mapping of LLB build steps, defined in the `buildConfig.llbDefinition` field, to their -original source code (for example, Dockerfile commands). The `source.locations` field contains the ranges -of all the Dockerfile commands that were executed in an LLB step. `source.infos` array contains the source -code itself. This mapping is present if the BuildKit frontend provided it when creating the LLB definition. - -#### `layers` - -Only included with `mode=max`. - -Defines the layer mapping of LLB build step mounts defined in `buildConfig.llbDefinition` to the -OCI descriptors of equivalent layers. This mapping is present if the layer data was available, usually -when attestation is for an image or if the build step pulled in image data as part of the build. - -#### `vcs` - -Included with both `mode=min` and `mode=max`. - -Defines optional metadata for the version control system used for the build. If a build uses a -remote context from Git repository, BuildKit extracts the details of the version control -system automatically and displays it in the `invocation.configSource` field. But if the build -uses a source from a local directory, the VCS information is lost even if the directory contained -a Git repository. In this case, the build client can send additional `vcs:source` and -`vcs:revision` build options and BuildKit will add them to the provenance attestations as extra -metadata. Note that, contrary to the `invocation.configSource` field, BuildKit doesn't verify the -`vcs` values, and as such they can't be trusted and should only be used as a metadata hint. - -```bash -buildctl build \ - --frontend=dockerfile.v0 \ - --local context=. \ - --local dockerfile=. \ - --opt attest:provenance= \ - --opt vcs:source=https://github.com/moby/buildkit \ - --opt vcs:revision=0c9b5aeb269c740650786ba77d882b0259415ec7 -``` - -## Output - -To inspect the provenance that was generated and attached to a container -image, you can use the `docker buildx imagetools` command to inspect the -image in a registry. Inspecting the attestation displays the format described -in the [attestation storage specification](./attestation-storage.md). - -For example, inspecting a simple Docker image based on `alpine:latest` results -in a provenance attestation similar to the following, for a `mode=min` build: - -```json -{ - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", - "subject": [ - { - "name": "pkg:docker//@?platform=", - "digest": { - "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" - } - } - ], - "predicate": { - "builder": { - "id": "" - }, - "buildType": "https://mobyproject.org/buildkit@v1", - "materials": [ - { - "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", - "digest": { - "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" - } - } - ], - "invocation": { - "configSource": { - "entryPoint": "Dockerfile" - }, - "parameters": { - "frontend": "dockerfile.v0", - "args": {}, - "locals": [ - { - "name": "context" - }, - { - "name": "dockerfile" - } - ] - }, - "environment": { - "platform": "linux/amd64" - } - }, - "metadata": { - "buildInvocationID": "yirbp1aosi1vqjmi3z6bc75nb", - "buildStartedOn": "2022-12-08T11:48:59.466513707Z", - "buildFinishedOn": "2022-12-08T11:49:01.256820297Z", - "reproducible": false, - "completeness": { - "parameters": true, - "environment": true, - "materials": false - }, - "https://mobyproject.org/buildkit@v1#metadata": {} - } - } -} -``` - -For a similar build, but with `mode=max`: - -```json -{ - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", - "subject": [ - { - "name": "pkg:docker//@?platform=", - "digest": { - "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" - } - } - ], - "predicate": { - "builder": { - "id": "" - }, - "buildType": "https://mobyproject.org/buildkit@v1", - "materials": [ - { - "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", - "digest": { - "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" - } - } - ], - "invocation": { - "configSource": { - "entryPoint": "Dockerfile" - }, - "parameters": { - "frontend": "dockerfile.v0", - "args": {}, - "locals": [ - { - "name": "context" - }, - { - "name": "dockerfile" - } - ] - }, - "environment": { - "platform": "linux/amd64" - } - }, - "buildConfig": { - "llbDefinition": [ - { - "id": "step0", - "op": { - "Op": { - "source": { - "identifier": "docker-image://docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" - } - }, - "platform": { - "Architecture": "amd64", - "OS": "linux" - }, - "constraints": {} - } - }, - { - "id": "step1", - "op": { - "Op": null - }, - "inputs": [ - "step0:0" - ] - } - ] - }, - "metadata": { - "buildInvocationID": "46ue2x93k3xj5l463dektwldw", - "buildStartedOn": "2022-12-08T11:50:54.953375437Z", - "buildFinishedOn": "2022-12-08T11:50:55.447841328Z", - "reproducible": false, - "completeness": { - "parameters": true, - "environment": true, - "materials": false - }, - "https://mobyproject.org/buildkit@v1#metadata": { - "source": { - "locations": { - "step0": { - "locations": [ - { - "ranges": [ - { - "start": { - "line": 1 - }, - "end": { - "line": 1 - } - } - ] - } - ] - } - }, - "infos": [ - { - "filename": "Dockerfile", - "data": "RlJPTSBhbHBpbmU6bGF0ZXN0Cg==", - "llbDefinition": [ - { - "id": "step0", - "op": { - "Op": { - "source": { - "identifier": "local://dockerfile", - "attrs": { - "local.differ": "none", - "local.followpaths": "[\"Dockerfile\",\"Dockerfile.dockerignore\",\"dockerfile\"]", - "local.session": "q2jnwdkas0i0iu4knchd92jaz", - "local.sharedkeyhint": "dockerfile" - } - } - }, - "constraints": {} - } - }, - { - "id": "step1", - "op": { - "Op": null - }, - "inputs": [ - "step0:0" - ] - } - ] - } - ] - }, - "layers": { - "step0:0": [ - [ - { - "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", - "digest": "sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad715", - "size": 3370706 - } - ] - ] - } - } - } - } -} -``` From ef284aaaf4939beb3ea9036de6416a8ee122fca4 Mon Sep 17 00:00:00 2001 From: David Karlsson Date: Tue, 3 Jan 2023 17:36:12 +0100 Subject: [PATCH 25/39] docs: slsa review updates Signed-off-by: David Karlsson (cherry picked from commit 4664605c4d7565a3eccfb313bc22b1fa2fec9778) --- docs/slsa-definitions.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/slsa-definitions.md b/docs/slsa-definitions.md index b0b667fb190e..9bd7995e6555 100644 --- a/docs/slsa-definitions.md +++ b/docs/slsa-definitions.md @@ -8,7 +8,7 @@ included when you generate attestations `mode=min` and `mode=max`. ## `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. The `builder.id` field is set to the URL of the build, if available. @@ -22,7 +22,7 @@ This value can be set using the `builder-id` attestation parameter. ## `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. The `buildType` field is set to `https://mobyproject.org/buildkit@v1` can be used to determine the structure of the provenance content. @@ -33,7 +33,7 @@ used to determine the structure of the provenance content. ## `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Describes the config that initialized the build. @@ -49,8 +49,8 @@ Describes the config that initialized the build. ``` For builds initialized from a remote context, like a Git or HTTP URL, this -object defines to URL to the context and its immutable digest. For builds using -a local frontend, such as a Dockerfile, the `entryPoint` field defines the path +object defines the context URL and its immutable digest in the `uri` and `digest` fields. +For builds using a local frontend, such as a Dockerfile, the `entryPoint` field defines the path for the frontend file that initialized the build (`filename` frontend option). ## `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) @@ -103,7 +103,7 @@ The following fields are only included with `mode=max`: ## `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. ```json "environment": { @@ -117,7 +117,7 @@ can be determined from the `in-toto` subject field. ## `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Defines all the external artifacts that were part of the build. The value depends on the type of artifact: @@ -202,7 +202,7 @@ field for every step. ## `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Unique identifier for the build invocation. When building a multi-platform image with a single build request, this value will be the shared by all the platform @@ -215,7 +215,7 @@ versions of the image. ## `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Timestamp when the build started. @@ -225,7 +225,7 @@ Timestamp when the build started. ## `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Timestamp when the build finished. @@ -235,7 +235,7 @@ Timestamp when the build finished. ## `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) -Included with `mode=min`. +Included with `mode=min` and `mode=max`. Defines if the provenance information is complete. @@ -273,7 +273,7 @@ value can be set by the user with the `reproducible=true` attestation parameter. ## `metadata.https://mobyproject.org/buildkit@v1#hermetic` -Included with `mode=min`. +Included with `mode=min` and `mode=max`. This extension field is set to true if the build was hermetic and did not access the network. In Dockerfiles, a build is hermetic if it does not use `RUN` @@ -314,7 +314,7 @@ for an image or if the build step pulled in image data as part of the build. ### `vcs` -Included with both `mode=min` and `mode=max`. +Included with `mode=min` and `mode=max`. Defines optional metadata for the version control system used for the build. If a build uses a remote context from Git repository, BuildKit extracts the details From 17a6833d6c89235237d669b8d3bee289c024847c Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 10:49:23 +0000 Subject: [PATCH 26/39] docs: add cross-linking between slsa pages Signed-off-by: Justin Chadwell (cherry picked from commit 065e6a5938294f3b5cf7368663cdd3ab4b1b9a7b) --- docs/slsa-definitions.md | 3 +++ docs/slsa.md | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/slsa-definitions.md b/docs/slsa-definitions.md index 9bd7995e6555..ee2fcce4fdff 100644 --- a/docs/slsa-definitions.md +++ b/docs/slsa-definitions.md @@ -1,5 +1,8 @@ # SLSA definitions +BuildKit supports the [creation of SLSA Provenance](./slsa.md) for builds that +it runs. + The provenance format generated by BuildKit is defined by the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). diff --git a/docs/slsa.md b/docs/slsa.md index cab9d94db1b2..a1437e717027 100644 --- a/docs/slsa.md +++ b/docs/slsa.md @@ -1,9 +1,8 @@ # SLSA provenance -BuildKit supports automatic creation of [SLSA Provenance](https://slsa.dev/provenance) -attestations for the build process. Provenance attestations record information describing -how a build was created, and is important for tracking the security of your software -artifacts. +BuildKit supports automatic creation of provenance attestations for the build +process. Provenance attestations record information describing how a build was +created, and is important for tracking the security of your software artifacts. Provenance attestations created by BuildKit include details such as: From feef8de9b9f3eefbbf19bf354bd20481fe782d0f Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 10:56:11 +0000 Subject: [PATCH 27/39] docs: tidy up json examples for slsa definitions Signed-off-by: Justin Chadwell (cherry picked from commit 618e8f219a043ec57c1a82f56a1675d537953911) --- docs/slsa-definitions.md | 108 +++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/docs/slsa-definitions.md b/docs/slsa-definitions.md index ee2fcce4fdff..e3bf0760783a 100644 --- a/docs/slsa-definitions.md +++ b/docs/slsa-definitions.md @@ -30,7 +30,7 @@ Included with `mode=min` and `mode=max`. The `buildType` field is set to `https://mobyproject.org/buildkit@v1` can be used to determine the structure of the provenance content. -``` +```json "buildType": "https://mobyproject.org/buildkit@v1", ``` @@ -49,6 +49,8 @@ Describes the config that initialized the build. }, "entryPoint": "Dockerfile" }, + ... + }, ``` For builds initialized from a remote context, like a Git or HTTP URL, this @@ -64,7 +66,6 @@ Describes build inputs passed to the build. ```json "invocation": { - ... "parameters": { "frontend": "gateway.v0", "args": { @@ -77,11 +78,14 @@ Describes build inputs passed to the build. { "id": "GIT_AUTH_HEADER", "optional": true - } + }, ... ], "ssh": [], - "locals": [], + "locals": [] + }, + ... + }, ``` The following fields are included with both `mode=min` and `mode=max`: @@ -109,9 +113,12 @@ The following fields are only included with `mode=max`: Included with `mode=min` and `mode=max`. ```json + "invocation": { "environment": { "platform": "linux/amd64" - } + }, + ... + }, ``` The only value BuildKit currently sets is the `platform` of the current build @@ -150,7 +157,7 @@ determine if the artifact has been updated compared to when the build ran. "digest": { "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" } - } + }, ... ], ``` @@ -171,36 +178,38 @@ field for every step. ```json "buildConfig": { - "llbDefinition": [ - { - "id": "step0", - "op": { - "Op": { - "exec": { - "meta": { - "args": [ - "/bin/sh", - "-c", - "go build ." - ], - "env": [ - "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "GOPATH=/go", - "GOFLAGS=-mod=vendor", - ], - "cwd": "/src", - }, - "mounts": [...] - } - }, - "platform": {...}, + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "exec": { + "meta": { + "args": [ + "/bin/sh", + "-c", + "go build ." + ], + "env": [ + "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "GOPATH=/go", + "GOFLAGS=-mod=vendor", + ], + "cwd": "/src", + }, + "mounts": [...] + } }, - "inputs": [ - "step8:0", - "step2:0", - ] + "platform": {...}, }, - ... + "inputs": [ + "step8:0", + "step2:0", + ] + }, + ... + ] + }, ``` ## `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) @@ -214,6 +223,8 @@ versions of the image. ```json "metadata": { "buildInvocationID": "rpv7a389uzil5lqmrgwhijwjz", + ... + }, ``` ## `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) @@ -223,7 +234,10 @@ Included with `mode=min` and `mode=max`. Timestamp when the build started. ```json + "metadata": { "buildStartedOn": "2021-11-17T15:00:00Z", + ... + }, ``` ## `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) @@ -233,7 +247,10 @@ Included with `mode=min` and `mode=max`. Timestamp when the build finished. ```json + "metadata": { "buildFinishedOn": "2021-11-17T15:01:00Z", + ... + }, ``` ## `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) @@ -257,12 +274,14 @@ repository all materials can be tracked by BuildKit and `completeness.materials` is true. ```json + "metadata": { "completeness": { "parameters": true, "environment": true, "materials": true - } - } + }, + ... + }, ``` ## `metadata.reproducible` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.reproducible) @@ -271,7 +290,10 @@ Defines if the build result is supposed to be byte-by-byte reproducible. This value can be set by the user with the `reproducible=true` attestation parameter. ```json - "reproducible": false + "metadata": { + "reproducible": false, + ... + }, ``` ## `metadata.https://mobyproject.org/buildkit@v1#hermetic` @@ -286,6 +308,7 @@ commands or disables network with `--network=none` flag. "metadata": { "https://mobyproject.org/buildkit@v1#hermetic": true, ... + }, ``` ## `metadata.https://mobyproject.org/buildkit@v1#metadata` @@ -295,6 +318,17 @@ Partially included with `mode=min`. This extension field defines BuildKit-specific additional metadata that is not part of the SLSA provenance spec. +```json + "metadata": { + "https://mobyproject.org/buildkit@v1#metadata": { + "source": {...}, + "layers": {...}, + "vcs": {...}, + }, + ... + }, +``` + ### `source` Only included with `mode=max`. From 82e05684d2ef6157175ae13b398e0cdc691e917d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 12:37:30 +0000 Subject: [PATCH 28/39] docs: rename slsa.md to slsa-provenance.md Signed-off-by: Justin Chadwell (cherry picked from commit 5845707046668af551e0f1ee7227fed1e576f7b2) --- docs/slsa-definitions.md | 2 +- docs/{slsa.md => slsa-provenance.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{slsa.md => slsa-provenance.md} (100%) diff --git a/docs/slsa-definitions.md b/docs/slsa-definitions.md index e3bf0760783a..76f25d9847b9 100644 --- a/docs/slsa-definitions.md +++ b/docs/slsa-definitions.md @@ -1,6 +1,6 @@ # SLSA definitions -BuildKit supports the [creation of SLSA Provenance](./slsa.md) for builds that +BuildKit supports the [creation of SLSA Provenance](./slsa-provenance.md) for builds that it runs. The provenance format generated by BuildKit is defined by the diff --git a/docs/slsa.md b/docs/slsa-provenance.md similarity index 100% rename from docs/slsa.md rename to docs/slsa-provenance.md From eb58c40abfcaa1934fbf9aea1c68b63d14437a36 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 13:38:16 +0000 Subject: [PATCH 29/39] docs: move attestation docs to dedicated directory There are quite a few pages dedicated to describing attestations, so for easier organization, we can move them all to a single location. Signed-off-by: Justin Chadwell (cherry picked from commit 4cfa60eebdd49f4ae4d769f3a4bd32c9576ca2c5) --- docs/{ => attestations}/attestation-storage.md | 0 docs/{ => attestations}/sbom-protocol.md | 0 docs/{ => attestations}/sbom.md | 0 docs/{ => attestations}/slsa-definitions.md | 0 docs/{ => attestations}/slsa-provenance.md | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename docs/{ => attestations}/attestation-storage.md (100%) rename docs/{ => attestations}/sbom-protocol.md (100%) rename docs/{ => attestations}/sbom.md (100%) rename docs/{ => attestations}/slsa-definitions.md (100%) rename docs/{ => attestations}/slsa-provenance.md (100%) diff --git a/docs/attestation-storage.md b/docs/attestations/attestation-storage.md similarity index 100% rename from docs/attestation-storage.md rename to docs/attestations/attestation-storage.md diff --git a/docs/sbom-protocol.md b/docs/attestations/sbom-protocol.md similarity index 100% rename from docs/sbom-protocol.md rename to docs/attestations/sbom-protocol.md diff --git a/docs/sbom.md b/docs/attestations/sbom.md similarity index 100% rename from docs/sbom.md rename to docs/attestations/sbom.md diff --git a/docs/slsa-definitions.md b/docs/attestations/slsa-definitions.md similarity index 100% rename from docs/slsa-definitions.md rename to docs/attestations/slsa-definitions.md diff --git a/docs/slsa-provenance.md b/docs/attestations/slsa-provenance.md similarity index 100% rename from docs/slsa-provenance.md rename to docs/attestations/slsa-provenance.md From c67b3d7cf54f04afd8c9faeb8310739f8387d0e6 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 13:39:09 +0000 Subject: [PATCH 30/39] docs: add index page for attestations Signed-off-by: Justin Chadwell (cherry picked from commit 1433d809b176d1e27879a3e42559b141bd391908) --- docs/attestations/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/attestations/README.md diff --git a/docs/attestations/README.md b/docs/attestations/README.md new file mode 100644 index 000000000000..b4e4c1e76449 --- /dev/null +++ b/docs/attestations/README.md @@ -0,0 +1,16 @@ +# Attestations + +BuildKit supports creating and attaching attestations to build artifacts. +Generated attestations use the [in-toto attestation format](https://github.com/in-toto/attestation). + +The currently supported attestation types are: + +- [SBOMs](./sbom.md) +- [SLSA Provenance](./slsa-provenance.md) + +Upon generation, attestations are attached differently to the export result: + +- For the `image`, `oci` and `docker` exporters, attestations are exported + using the attached [attestation storage](./attestation-storage.md). +- For the `local` and `tar` exporters, attestations are written to separate + files within the output directory. From 2d1b3bac16caff8c3ee48c555bd8cac33feb724d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 16 Dec 2022 13:46:49 +0000 Subject: [PATCH 31/39] attestation: only supplement file data for the core scan Previously, we would attempt to add file data for every single SBOM - however, if these SBOMs were taken of layers that were not exported, then these could be wrong. To workaround this, for the file layer details to be added to the resulting SBOM, we require that the scanner add a metadata property to indicate the default value. This is configurable, since in the future we may want behavior that allows the frontend to specify no file layers, or wants an SBOM with layers other than the default. Signed-off-by: Justin Chadwell (cherry picked from commit 2948389c3e949a3d9cc96302dbeb5e5e59412481) --- docs/attestations/sbom-protocol.md | 5 ++--- exporter/containerimage/attestations.go | 8 ++++++++ frontend/attestations/sbom/sbom.go | 1 + solver/result/attestation.go | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/attestations/sbom-protocol.md b/docs/attestations/sbom-protocol.md index 47b623ea95df..1505fbfffcca 100644 --- a/docs/attestations/sbom-protocol.md +++ b/docs/attestations/sbom-protocol.md @@ -48,9 +48,8 @@ by BuildKit: This variable specifies the main target, passing the path to the root filesystem of the final build result. - The scanner should scan this filesystem, and write its SBOM scans to - `$BUILDKIT_SCAN_DESTINATION/.spdx.json`. If the scan name is not - significant the scan can be named `$(basename $BUILDKIT_SCAN_SOURCE)`. + The scanner should scan this filesystem, and write its SBOM result to + `$BUILDKIT_SCAN_DESTINATION/$(basename $BUILDKIT_SCAN_SOURCE).spdx.json`. - `BUILDKIT_SCAN_SOURCE_EXTRAS` (optional) diff --git a/exporter/containerimage/attestations.go b/exporter/containerimage/attestations.go index c9eef91c0c4a..782c18733035 100644 --- a/exporter/containerimage/attestations.go +++ b/exporter/containerimage/attestations.go @@ -14,6 +14,7 @@ import ( gatewaypb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/session" "github.com/moby/buildkit/solver" + "github.com/moby/buildkit/solver/result" "github.com/moby/buildkit/version" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -35,6 +36,13 @@ func supplementSBOM(ctx context.Context, s session.Group, target cache.Immutable if att.InToto.PredicateType != intoto.PredicateSPDX { return att, nil } + name, ok := att.Metadata[result.AttestationSBOMCore] + if !ok { + return att, nil + } + if n, _, _ := strings.Cut(att.Path, "."); n != string(name) { + return att, nil + } content, err := attestation.ReadAll(ctx, s, att) if err != nil { diff --git a/frontend/attestations/sbom/sbom.go b/frontend/attestations/sbom/sbom.go index 4d86f74140bf..b4446aed45f0 100644 --- a/frontend/attestations/sbom/sbom.go +++ b/frontend/attestations/sbom/sbom.go @@ -89,6 +89,7 @@ func CreateSBOMScanner(ctx context.Context, resolver llb.ImageMetaResolver, scan Ref: stsbom, Metadata: map[string][]byte{ result.AttestationReasonKey: []byte(result.AttestationReasonSBOM), + result.AttestationSBOMCore: []byte(CoreSBOMName), }, InToto: result.InTotoAttestation{ PredicateType: intoto.PredicateSPDX, diff --git a/solver/result/attestation.go b/solver/result/attestation.go index 450016ffab70..77af74da1906 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -9,6 +9,7 @@ import ( const ( AttestationReasonKey = "reason" + AttestationSBOMCore = "sbom-core" AttestationInlineOnlyKey = "inline-only" ) From 1c222b64b8fedadb6880e67d4d9e6e09c8a51c1d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 14 Dec 2022 18:26:39 +0000 Subject: [PATCH 32/39] ociindex: refactor to hide implementation internally This allows some of the client processing code to abstract more over the details of how the ociindex package works, and how it loads tags - the calling code only need to know that it's being pointed at a containerd content store directory. Importantly, this allows for reusability, so we can use this same code more easily in buildx for oci-layouts. Signed-off-by: Justin Chadwell (cherry picked from commit 49c2275f65d1b1abaaf0198dc63a48325bc5b956) --- client/ociindex/ociindex.go | 141 ++++++++++++++++++++++-------------- client/solve.go | 60 +++++++-------- 2 files changed, 118 insertions(+), 83 deletions(-) diff --git a/client/ociindex/ociindex.go b/client/ociindex/ociindex.go index ef1e1cf4c65e..b4db093798a2 100644 --- a/client/ociindex/ociindex.go +++ b/client/ociindex/ociindex.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "os" + "path" "github.com/gofrs/flock" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" @@ -11,69 +12,86 @@ import ( ) const ( - // IndexJSONLockFileSuffix is the suffix of the lock file - IndexJSONLockFileSuffix = ".lock" + // indexFile is the name of the index file + indexFile = "index.json" + + // lockFileSuffix is the suffix of the lock file + lockFileSuffix = ".lock" ) -// PutDescToIndex puts desc to index with tag. -// Existing manifests with the same tag will be removed from the index. -func PutDescToIndex(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) error { - if index == nil { - index = &ocispecs.Index{} +type StoreIndex struct { + indexPath string + lockPath string +} + +func NewStoreIndex(storePath string) StoreIndex { + indexPath := path.Join(storePath, indexFile) + return StoreIndex{ + indexPath: indexPath, + lockPath: indexPath + lockFileSuffix, } - if index.SchemaVersion == 0 { - index.SchemaVersion = 2 +} + +func (s StoreIndex) Read() (*ocispecs.Index, error) { + lock := flock.New(s.lockPath) + locked, err := lock.TryRLock() + if err != nil { + return nil, errors.Wrapf(err, "could not lock %s", s.lockPath) } - if tag != "" { - if desc.Annotations == nil { - desc.Annotations = make(map[string]string) - } - desc.Annotations[ocispecs.AnnotationRefName] = tag - // remove existing manifests with the same tag - var manifests []ocispecs.Descriptor - for _, m := range index.Manifests { - if m.Annotations[ocispecs.AnnotationRefName] != tag { - manifests = append(manifests, m) - } - } - index.Manifests = manifests + if !locked { + return nil, errors.Errorf("could not lock %s", s.lockPath) } - index.Manifests = append(index.Manifests, desc) - return nil + defer func() { + lock.Unlock() + os.RemoveAll(s.lockPath) + }() + + b, err := os.ReadFile(s.indexPath) + if err != nil { + return nil, errors.Wrapf(err, "could not read %s", s.indexPath) + } + var idx ocispecs.Index + if err := json.Unmarshal(b, &idx); err != nil { + return nil, errors.Wrapf(err, "could not unmarshal %s (%q)", s.indexPath, string(b)) + } + return &idx, nil } -func PutDescToIndexJSONFileLocked(indexJSONPath string, desc ocispecs.Descriptor, tag string) error { - lockPath := indexJSONPath + IndexJSONLockFileSuffix - lock := flock.New(lockPath) +func (s StoreIndex) Put(tag string, desc ocispecs.Descriptor) error { + lock := flock.New(s.lockPath) locked, err := lock.TryLock() if err != nil { - return errors.Wrapf(err, "could not lock %s", lockPath) + return errors.Wrapf(err, "could not lock %s", s.lockPath) } if !locked { - return errors.Errorf("could not lock %s", lockPath) + return errors.Errorf("could not lock %s", s.lockPath) } defer func() { lock.Unlock() - os.RemoveAll(lockPath) + os.RemoveAll(s.lockPath) }() - f, err := os.OpenFile(indexJSONPath, os.O_RDWR|os.O_CREATE, 0644) + + f, err := os.OpenFile(s.indexPath, os.O_RDWR|os.O_CREATE, 0644) if err != nil { - return errors.Wrapf(err, "could not open %s", indexJSONPath) + return errors.Wrapf(err, "could not open %s", s.indexPath) } defer f.Close() + var idx ocispecs.Index b, err := io.ReadAll(f) if err != nil { - return errors.Wrapf(err, "could not read %s", indexJSONPath) + return errors.Wrapf(err, "could not read %s", s.indexPath) } if len(b) > 0 { if err := json.Unmarshal(b, &idx); err != nil { - return errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b)) + return errors.Wrapf(err, "could not unmarshal %s (%q)", s.indexPath, string(b)) } } - if err = PutDescToIndex(&idx, desc, tag); err != nil { + + if err = insertDesc(&idx, desc, tag); err != nil { return err } + b, err = json.Marshal(idx) if err != nil { return err @@ -87,27 +105,44 @@ func PutDescToIndexJSONFileLocked(indexJSONPath string, desc ocispecs.Descriptor return nil } -func ReadIndexJSONFileLocked(indexJSONPath string) (*ocispecs.Index, error) { - lockPath := indexJSONPath + IndexJSONLockFileSuffix - lock := flock.New(lockPath) - locked, err := lock.TryRLock() +func (s StoreIndex) Get(tag string) (*ocispecs.Descriptor, error) { + idx, err := s.Read() if err != nil { - return nil, errors.Wrapf(err, "could not lock %s", lockPath) + return nil, err } - if !locked { - return nil, errors.Errorf("could not lock %s", lockPath) + + for _, m := range idx.Manifests { + if t, ok := m.Annotations[ocispecs.AnnotationRefName]; ok && t == tag { + return &m, nil + } } - defer func() { - lock.Unlock() - os.RemoveAll(lockPath) - }() - b, err := os.ReadFile(indexJSONPath) - if err != nil { - return nil, errors.Wrapf(err, "could not read %s", indexJSONPath) + return nil, nil +} + +// insertDesc puts desc to index with tag. +// Existing manifests with the same tag will be removed from the index. +func insertDesc(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) error { + if index == nil { + return nil } - var idx ocispecs.Index - if err := json.Unmarshal(b, &idx); err != nil { - return nil, errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b)) + + if index.SchemaVersion == 0 { + index.SchemaVersion = 2 } - return &idx, nil + if tag != "" { + if desc.Annotations == nil { + desc.Annotations = make(map[string]string) + } + desc.Annotations[ocispecs.AnnotationRefName] = tag + // remove existing manifests with the same tag + var manifests []ocispecs.Descriptor + for _, m := range index.Manifests { + if m.Annotations[ocispecs.AnnotationRefName] != tag { + manifests = append(manifests, m) + } + } + index.Manifests = manifests + } + index.Manifests = append(index.Manifests, desc) + return nil } diff --git a/client/solve.go b/client/solve.go index 67129957b1df..653b00a66df2 100644 --- a/client/solve.go +++ b/client/solve.go @@ -129,7 +129,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG ex = opt.Exports[0] } - indicesToUpdate := []string{} + storesToUpdate := []string{} if !opt.SessionPreInitialized { if len(syncedDirs) > 0 { @@ -194,7 +194,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG return nil, err } contentStores["export"] = cs - indicesToUpdate = append(indicesToUpdate, filepath.Join(ex.OutputDir, "index.json")) + storesToUpdate = append(storesToUpdate, ex.OutputDir) default: s.Allow(filesync.NewFSSyncTargetDir(ex.OutputDir)) } @@ -327,8 +327,9 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG if err = json.Unmarshal([]byte(manifestDescJSON), &manifestDesc); err != nil { return nil, err } - for indexJSONPath, tag := range cacheOpt.indicesToUpdate { - if err = ociindex.PutDescToIndexJSONFileLocked(indexJSONPath, manifestDesc, tag); err != nil { + for storePath, tag := range cacheOpt.storesToUpdate { + idx := ociindex.NewStoreIndex(storePath) + if err := idx.Put(tag, manifestDesc); err != nil { return nil, err } } @@ -342,12 +343,13 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG if err = json.Unmarshal([]byte(manifestDescDt), &manifestDesc); err != nil { return nil, err } - for _, indexJSONPath := range indicesToUpdate { + for _, storePath := range storesToUpdate { tag := "latest" if t, ok := res.ExporterResponse["image.name"]; ok { tag = t } - if err = ociindex.PutDescToIndexJSONFileLocked(indexJSONPath, manifestDesc, tag); err != nil { + idx := ociindex.NewStoreIndex(storePath) + if err := idx.Put(tag, manifestDesc); err != nil { return nil, err } } @@ -406,10 +408,10 @@ func defaultSessionName() string { } type cacheOptions struct { - options controlapi.CacheOptions - contentStores map[string]content.Store // key: ID of content store ("local:" + csDir) - indicesToUpdate map[string]string // key: index.JSON file name, value: tag - frontendAttrs map[string]string + options controlapi.CacheOptions + contentStores map[string]content.Store // key: ID of content store ("local:" + csDir) + storesToUpdate map[string]string // key: path to content store, value: tag + frontendAttrs map[string]string } func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cacheOptions, error) { @@ -418,7 +420,7 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach cacheImports []*controlapi.CacheOptionsEntry ) contentStores := make(map[string]content.Store) - indicesToUpdate := make(map[string]string) // key: index.JSON file name, value: tag + storesToUpdate := make(map[string]string) frontendAttrs := make(map[string]string) for _, ex := range opt.CacheExports { if ex.Type == "local" { @@ -440,8 +442,7 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach tag = t } // TODO(AkihiroSuda): support custom index JSON path and tag - indexJSONPath := filepath.Join(csDir, "index.json") - indicesToUpdate[indexJSONPath] = tag + storesToUpdate[csDir] = tag } if ex.Type == "registry" { regRef := ex.Attrs["ref"] @@ -465,27 +466,26 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach bklog.G(ctx).Warning("local cache import at " + csDir + " not found due to err: " + err.Error()) continue } - // if digest is not specified, load from "latest" tag + // if digest is not specified, attempt to load from tag if im.Attrs["digest"] == "" { - idx, err := ociindex.ReadIndexJSONFileLocked(filepath.Join(csDir, "index.json")) + tag := "latest" + if t, ok := im.Attrs["tag"]; ok { + tag = t + } + + idx := ociindex.NewStoreIndex(csDir) + desc, err := idx.Get(tag) if err != nil { bklog.G(ctx).Warning("local cache import at " + csDir + " not found due to err: " + err.Error()) continue } - for _, m := range idx.Manifests { - tag := "latest" - if t, ok := im.Attrs["tag"]; ok { - tag = t - } - if m.Annotations[ocispecs.AnnotationRefName] == tag { - im.Attrs["digest"] = string(m.Digest) - break - } - } - if im.Attrs["digest"] == "" { - return nil, errors.New("local cache importer requires either explicit digest, \"latest\" tag or custom tag on index.json") + if desc != nil { + im.Attrs["digest"] = desc.Digest.String() } } + if im.Attrs["digest"] == "" { + return nil, errors.New("local cache importer requires either explicit digest, \"latest\" tag or custom tag on index.json") + } contentStores["local:"+csDir] = cs } if im.Type == "registry" { @@ -513,9 +513,9 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach Exports: cacheExports, Imports: cacheImports, }, - contentStores: contentStores, - indicesToUpdate: indicesToUpdate, - frontendAttrs: frontendAttrs, + contentStores: contentStores, + storesToUpdate: storesToUpdate, + frontendAttrs: frontendAttrs, } return &res, nil } From 98249a90323f03809b58ce04930fa86ced51b9c5 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 14 Dec 2022 18:29:00 +0000 Subject: [PATCH 33/39] ociindex: add utility method for getting a single manifest from the index Signed-off-by: Justin Chadwell (cherry picked from commit 96230176e9d4b476ef106e6f40948a76f1dbb175) --- client/ociindex/ociindex.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/ociindex/ociindex.go b/client/ociindex/ociindex.go index b4db093798a2..3731ff36bb23 100644 --- a/client/ociindex/ociindex.go +++ b/client/ociindex/ociindex.go @@ -119,6 +119,18 @@ func (s StoreIndex) Get(tag string) (*ocispecs.Descriptor, error) { return nil, nil } +func (s StoreIndex) GetSingle() (*ocispecs.Descriptor, error) { + idx, err := s.Read() + if err != nil { + return nil, err + } + + if len(idx.Manifests) == 1 { + return &idx.Manifests[0], nil + } + return nil, nil +} + // insertDesc puts desc to index with tag. // Existing manifests with the same tag will be removed from the index. func insertDesc(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) error { From d08d4a4b35d6a728161608661104fba24ab79755 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 29 Dec 2022 15:30:13 -0800 Subject: [PATCH 34/39] progress: fix clean context cancelling Signed-off-by: Tonis Tiigi (cherry picked from commit c905063303f916662b0baad22ca0be856b275cc8) --- util/pull/pullprogress/progress.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/pull/pullprogress/progress.go b/util/pull/pullprogress/progress.go index b743706ed955..93c50106f79d 100644 --- a/util/pull/pullprogress/progress.go +++ b/util/pull/pullprogress/progress.go @@ -106,6 +106,8 @@ func trackProgress(ctx context.Context, desc ocispecs.Descriptor, manager PullMa select { case <-ctx.Done(): onFinalStatus = true + // we need a context for the manager.Status() calls to pass once. after that this function will exit + ctx = context.TODO() case <-ticker.C: } From 6de07e3ef87a288c1b8b2a1bf073a37bd6547b50 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 5 Jan 2023 20:31:49 -0800 Subject: [PATCH 35/39] vendor: update fsutil to fb43384 Signed-off-by: Tonis Tiigi (cherry picked from commit b3a430cf3f41bd30279bcec447ea170c1437d58e) --- go.mod | 2 +- go.sum | 4 ++-- vendor/github.com/tonistiigi/fsutil/send.go | 2 +- vendor/modules.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6d723f35befd..4dc34195436b 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f github.com/stretchr/testify v1.8.0 - github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf + github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 github.com/tonistiigi/go-archvariant v1.0.0 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea diff --git a/go.sum b/go.sum index b150443fcca1..7c859ea97572 100644 --- a/go.sum +++ b/go.sum @@ -1379,8 +1379,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1 github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= -github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf h1:2n2v98sRhXEG0Kh7+EvctaNIyOim36Ekp4pGDzbuvO8= -github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8= +github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa h1:XOFp/3aBXlqmOFAg3r6e0qQjPnK5I970LilqX+Is1W8= +github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8= github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 h1:8eY6m1mjgyB8XySUR7WvebTM8D/Vs86jLJzD/Tw7zkc= github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg= github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/aukdhCdTyb0= diff --git a/vendor/github.com/tonistiigi/fsutil/send.go b/vendor/github.com/tonistiigi/fsutil/send.go index 2c1a3801d58a..f1c51b83652d 100644 --- a/vendor/github.com/tonistiigi/fsutil/send.go +++ b/vendor/github.com/tonistiigi/fsutil/send.go @@ -135,7 +135,7 @@ func (s *sender) sendFile(h *sendHandle) error { defer f.Close() buf := bufPool.Get().(*[]byte) defer bufPool.Put(buf) - if _, err := io.CopyBuffer(&fileSender{sender: s, id: h.id}, f, *buf); err != nil { + if _, err := io.CopyBuffer(&fileSender{sender: s, id: h.id}, struct{ io.Reader }{f}, *buf); err != nil { return err } } diff --git a/vendor/modules.txt b/vendor/modules.txt index dc7358d0a9dc..79338b4e03a5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -676,7 +676,7 @@ github.com/spdx/tools-golang/spdx/v2_3 ## explicit; go 1.13 github.com/stretchr/testify/assert github.com/stretchr/testify/require -# github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf +# github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa ## explicit; go 1.18 github.com/tonistiigi/fsutil github.com/tonistiigi/fsutil/copy From dc43f7474306e5d5aaf6340518711e989d06fba0 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 5 Jan 2023 23:25:07 -0800 Subject: [PATCH 36/39] llbsolver: fix panic when requesting provenance on nil result Signed-off-by: Tonis Tiigi (cherry picked from commit 994b5291f8b5c2f2d2c57eb7f2dcf1edeeefb73e) --- .../dockerfile/dockerfile_provenance_test.go | 37 +++++++++++++++++++ frontend/dockerfile/dockerfile_test.go | 1 + solver/llbsolver/proc/provenance.go | 4 ++ 3 files changed, 42 insertions(+) diff --git a/frontend/dockerfile/dockerfile_provenance_test.go b/frontend/dockerfile/dockerfile_provenance_test.go index 2d737511e570..94f1374db0b1 100644 --- a/frontend/dockerfile/dockerfile_provenance_test.go +++ b/frontend/dockerfile/dockerfile_provenance_test.go @@ -862,3 +862,40 @@ RUN --mount=type=secret,id=mysecret --mount=type=secret,id=othersecret --mount=t require.Equal(t, "default", pred.Invocation.Parameters.SSH[0].ID) require.True(t, pred.Invocation.Parameters.SSH[0].Optional) } + +func testNilProvenance(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureProvenance) + ctx := sb.Context() + + c, err := client.New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + f := getFrontend(t, sb) + + dockerfile := []byte(` +FROM scratch +ENV FOO=bar +`) + dir, err := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + FrontendAttrs: map[string]string{ + "attest:provenance": "mode=max", + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterImage, + }, + }, + }, nil) + require.NoError(t, err) +} diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 336995cd4a11..f99ff1181d54 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -154,6 +154,7 @@ var allTests = integration.TestFuncs( testClientFrontendProvenance, testClientLLBProvenance, testSecretSSHProvenance, + testNilProvenance, testSBOMScannerArgs, ) diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index e6116c2eaafd..1af3af196028 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -32,6 +32,10 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { return nil, errors.Errorf("no build info found for provenance %s", p.ID) } + if cp == nil { + continue + } + ref, ok := res.FindRef(p.ID) if !ok { return nil, errors.Errorf("could not find ref %s", p.ID) From 0901e9373d8a25bcfd1bcc0e83f336b11bd2d95d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 6 Jan 2023 16:24:57 +0000 Subject: [PATCH 37/39] exporter: force enabling inline attestations for image export Signed-off-by: Justin Chadwell (cherry picked from commit e55f8b611f5e9ea7974b3a892539084bc193c798) --- exporter/containerimage/export.go | 3 ++- exporter/containerimage/opts.go | 2 ++ exporter/containerimage/writer.go | 31 +++++++++++++++---------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/exporter/containerimage/export.go b/exporter/containerimage/export.go index 9c5262377d3f..55eaf3ff5803 100644 --- a/exporter/containerimage/export.go +++ b/exporter/containerimage/export.go @@ -78,7 +78,8 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp RefCfg: cacheconfig.RefConfig{ Compression: compression.New(compression.Default), }, - BuildInfo: true, + BuildInfo: true, + ForceInlineAttestations: true, }, store: true, } diff --git a/exporter/containerimage/opts.go b/exporter/containerimage/opts.go index 057bd299e4f2..c8b00a765578 100644 --- a/exporter/containerimage/opts.go +++ b/exporter/containerimage/opts.go @@ -34,6 +34,8 @@ type ImageCommitOpts struct { BuildInfoAttrs bool Annotations AnnotationsGroup Epoch *time.Time + + ForceInlineAttestations bool // force inline attestations to be attached } func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error) { diff --git a/exporter/containerimage/writer.go b/exporter/containerimage/writer.go index c9b5d48b804e..068d86958f8f 100644 --- a/exporter/containerimage/writer.go +++ b/exporter/containerimage/writer.go @@ -69,22 +69,24 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session return nil, err } - requiredAttestations := false - for _, p := range ps.Platforms { - if atts, ok := inp.Attestations[p.ID]; ok { - atts = attestation.Filter(atts, nil, map[string][]byte{ - result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(true)), - }) - if len(atts) > 0 { - requiredAttestations = true - break + if !isMap { + // enable index if we need to include attestations + for _, p := range ps.Platforms { + if atts, ok := inp.Attestations[p.ID]; ok { + if !opts.ForceInlineAttestations { + // if we don't need force inline attestations (for oci + // exporter), filter them out + atts = attestation.Filter(atts, nil, map[string][]byte{ + result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(true)), + }) + } + if len(atts) > 0 { + isMap = true + break + } } } } - if requiredAttestations { - isMap = true - } - if opts.Epoch == nil { if tm, ok, err := epoch.ParseSource(inp); err != nil { return nil, err @@ -108,9 +110,6 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session if len(ps.Platforms) > 1 { return nil, errors.Errorf("cannot export multiple platforms without multi-platform enabled") } - if requiredAttestations { - return nil, errors.Errorf("cannot export attestations without multi-platform enabled") - } var ref cache.ImmutableRef var p exptypes.Platform From 09a94ed8a7d1d945dd13bf08e9190597552ad44d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 6 Jan 2023 17:00:09 +0000 Subject: [PATCH 38/39] exporter: allow configuring inline attestations for image exporters Signed-off-by: Justin Chadwell (cherry picked from commit 3eaf157027566c9706aeac3a3ec40e5c1fa9d431) --- exporter/containerimage/opts.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/exporter/containerimage/opts.go b/exporter/containerimage/opts.go index c8b00a765578..c12d86127e0f 100644 --- a/exporter/containerimage/opts.go +++ b/exporter/containerimage/opts.go @@ -12,13 +12,14 @@ import ( ) const ( - keyImageName = "name" - keyLayerCompression = "compression" - keyCompressionLevel = "compression-level" - keyForceCompression = "force-compression" - keyOCITypes = "oci-mediatypes" - keyBuildInfo = "buildinfo" - keyBuildInfoAttrs = "buildinfo-attrs" + keyImageName = "name" + keyLayerCompression = "compression" + keyCompressionLevel = "compression-level" + keyForceCompression = "force-compression" + keyOCITypes = "oci-mediatypes" + keyBuildInfo = "buildinfo" + keyBuildInfoAttrs = "buildinfo-attrs" + keyForceInlineAttestations = "attestation-inline" // preferNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was // already found to use a non-distributable media type. @@ -75,6 +76,8 @@ func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error) err = parseBoolWithDefault(&c.BuildInfo, k, v, true) case keyBuildInfoAttrs: err = parseBoolWithDefault(&c.BuildInfoAttrs, k, v, false) + case keyForceInlineAttestations: + err = parseBool(&c.ForceInlineAttestations, k, v) case keyPreferNondistLayers: err = parseBool(&c.RefCfg.PreferNonDistributable, k, v) default: From 4cbc41174fceac32ab48748838b1932b232715f0 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 6 Jan 2023 10:37:18 -0800 Subject: [PATCH 39/39] testutil: pin busybox and alpine used in releases Signed-off-by: Tonis Tiigi (cherry picked from commit 2f48f16b202f0a7c5740808c3f9f284a79104da5) --- util/testutil/integration/pins.go | 15 +++++++++++++++ util/testutil/integration/run.go | 10 ++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 util/testutil/integration/pins.go diff --git a/util/testutil/integration/pins.go b/util/testutil/integration/pins.go new file mode 100644 index 000000000000..32145f71714a --- /dev/null +++ b/util/testutil/integration/pins.go @@ -0,0 +1,15 @@ +package integration + +var pins = map[string]map[string]string{ + // busybox is pinned to 1.35. Newer produces has "illegal instruction" panic on some of Github infra on sha256sum + "busybox:latest": { + "amd64": "sha256:0d5a701f0ca53f38723108687add000e1922f812d4187dea7feaee85d2f5a6c5", + "arm64v8": "sha256:ffe38d75e44d8ffac4cd6d09777ffc31e94ea0ded6a0164e825a325dc17a3b68", + "library": "sha256:f4ed5f2163110c26d42741fdc92bd1710e118aed4edb19212548e8ca4e5fca22", + }, + "alpine:latest": { + "amd64": "sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc02380c", + "arm64v8": "sha256:af06af3514c44a964d3b905b498cf6493db8f1cde7c10e078213a89c87308ba0", + "library": "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4", + }, +} diff --git a/util/testutil/integration/run.go b/util/testutil/integration/run.go index 5168e087a766..18f6f0b748cd 100644 --- a/util/testutil/integration/run.go +++ b/util/testutil/integration/run.go @@ -264,12 +264,18 @@ func OfficialImages(names ...string) map[string]string { ns := runtime.GOARCH if ns == "arm64" { ns = "arm64v8" - } else if ns != "amd64" && ns != "armhf" { + } else if ns != "amd64" { ns = "library" } m := map[string]string{} for _, name := range names { - m["library/"+name] = "docker.io/" + ns + "/" + name + ref := "docker.io/" + ns + "/" + name + if pns, ok := pins[name]; ok { + if dgst, ok := pns[ns]; ok { + ref += "@" + dgst + } + } + m["library/"+name] = ref } return m }