From f1e5d695d390e0aae584cdfbfd9f9f76bfb5f607 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 30 Nov 2022 14:04:49 +0000 Subject: [PATCH 1/4] solver: add metadata to attestations We can now attach a reason for why each attestation was generated, which will in future allow us to include/exclude specific attestation types from exporters if desired. Signed-off-by: Justin Chadwell --- frontend/attestations/sbom/sbom.go | 3 +++ solver/llbsolver/proc/provenance.go | 3 +++ solver/result/attestation.go | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/frontend/attestations/sbom/sbom.go b/frontend/attestations/sbom/sbom.go index c13ed2b0c4c5..c5f07ea27c19 100644 --- a/frontend/attestations/sbom/sbom.go +++ b/frontend/attestations/sbom/sbom.go @@ -80,6 +80,9 @@ func CreateSBOMScanner(ctx context.Context, resolver llb.ImageMetaResolver, scan stsbom := runscan.AddMount(outDir, llb.Scratch()) return result.Attestation{ Kind: gatewaypb.AttestationKindBundle, + Metadata: map[string][]byte{ + result.AttestationReasonKey: result.AttestationReasonSBOM, + }, InToto: result.InTotoAttestation{ PredicateType: intoto.PredicateSPDX, }, diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index 1a60ca2b11ed..e1c13c8b65d6 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -145,6 +145,9 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { res.AddAttestation(p.ID, result.Attestation{ Kind: gatewaypb.AttestationKindInToto, + Metadata: map[string][]byte{ + result.AttestationReasonKey: result.AttestationReasonProvenance, + }, InToto: result.InTotoAttestation{ PredicateType: slsa.PredicateSLSAProvenance, }, diff --git a/solver/result/attestation.go b/solver/result/attestation.go index 81e8a09a5a99..64d7e535710f 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -5,9 +5,20 @@ import ( digest "github.com/opencontainers/go-digest" ) +const ( + AttestationReasonKey = "reason" +) + +var ( + AttestationReasonSBOM = []byte("sbom") + AttestationReasonProvenance = []byte("provenance") +) + type Attestation struct { Kind pb.AttestationKind + Metadata map[string][]byte + Ref string Path string ContentFunc func() ([]byte, error) From c93d520e3f87cfb3398322f509e4f1ce4b52071a Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 30 Nov 2022 14:06:52 +0000 Subject: [PATCH 2/4] provenance: remove explicit mode=disabled option Signed-off-by: Justin Chadwell --- solver/llbsolver/proc/provenance.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index e1c13c8b65d6..a5765a6229ca 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -57,8 +57,6 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { var mode string if v, ok := attrs["mode"]; ok { switch v { - case "disabled", "none": - return res, nil case "full": mode = "max" case "max", "min": From 3e3618b51955e34a486611815efe9126fcc6f9a8 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 30 Nov 2022 14:07:02 +0000 Subject: [PATCH 3/4] provenance: allow special inline option to include only in image outputs Signed-off-by: Justin Chadwell --- exporter/attestation/filter.go | 45 +++++++++++++++++++++++++++++ exporter/local/fs.go | 4 +++ solver/llbsolver/proc/provenance.go | 6 ++++ solver/result/attestation.go | 1 + 4 files changed, 56 insertions(+) create mode 100644 exporter/attestation/filter.go diff --git a/exporter/attestation/filter.go b/exporter/attestation/filter.go new file mode 100644 index 000000000000..edd80bae28d3 --- /dev/null +++ b/exporter/attestation/filter.go @@ -0,0 +1,45 @@ +package attestation + +import ( + "bytes" + + "github.com/moby/buildkit/solver/result" +) + +func Filter(attestations []result.Attestation, include map[string][]byte, exclude map[string][]byte) []result.Attestation { + if len(include) == 0 && len(exclude) == 0 { + return attestations + } + + result := []result.Attestation{} + for _, att := range attestations { + meta := att.Metadata + if meta == nil { + meta = map[string][]byte{} + } + + match := true + for k, v := range include { + if !bytes.Equal(meta[k], v) { + match = false + break + } + } + if !match { + continue + } + + for k, v := range exclude { + if bytes.Equal(meta[k], v) { + match = false + break + } + } + if !match { + continue + } + + result = append(result, att) + } + return result +} diff --git a/exporter/local/fs.go b/exporter/local/fs.go index be1469463734..a2026b55ea91 100644 --- a/exporter/local/fs.go +++ b/exporter/local/fs.go @@ -7,6 +7,7 @@ import ( "io/fs" "os" "path" + "strconv" "time" "github.com/docker/docker/pkg/idtools" @@ -87,6 +88,9 @@ func CreateFS(ctx context.Context, sessionID string, k string, ref cache.Immutab } outputFS := fsutil.NewFS(src, walkOpt) + attestations = attestation.Filter(attestations, nil, map[string][]byte{ + result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(true)), + }) attestations, err = attestation.Unbundle(ctx, session.NewGroup(sessionID), refs, attestations) if err != nil { return nil, nil, err diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index a5765a6229ca..3e7ce4a1e1e2 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -66,6 +66,11 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { } } + var inlineOnly bool + if v, err := strconv.ParseBool(attrs["inline-only"]); v && err == nil { + inlineOnly = true + } + for _, p := range ps.Platforms { cp, ok := res.Provenance.Refs[p.ID] if !ok { @@ -145,6 +150,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { Kind: gatewaypb.AttestationKindInToto, Metadata: map[string][]byte{ result.AttestationReasonKey: result.AttestationReasonProvenance, + result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(inlineOnly)), }, InToto: result.InTotoAttestation{ PredicateType: slsa.PredicateSLSAProvenance, diff --git a/solver/result/attestation.go b/solver/result/attestation.go index 64d7e535710f..e23e88ab3d18 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -7,6 +7,7 @@ import ( const ( AttestationReasonKey = "reason" + AttestationInlineOnlyKey = "inline-only" ) var ( From 8e107a623715f49ed655c13b9f3270a62e783585 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 6 Dec 2022 16:05:29 +0000 Subject: [PATCH 4/4] provenance: set default mode=max if not set Signed-off-by: Justin Chadwell --- .../dockerfile/dockerfile_provenance_test.go | 20 ++++++++++++------- solver/llbsolver/proc/provenance.go | 9 ++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/dockerfile/dockerfile_provenance_test.go b/frontend/dockerfile/dockerfile_provenance_test.go index 6729f7c1fcc6..e3ed00916f07 100644 --- a/frontend/dockerfile/dockerfile_provenance_test.go +++ b/frontend/dockerfile/dockerfile_provenance_test.go @@ -54,12 +54,18 @@ RUN echo "ok" > /foo ) require.NoError(t, err) - for _, mode := range []string{"min", "max"} { + for _, mode := range []string{"", "min", "max"} { t.Run(mode, func(t *testing.T) { - target := registry + "/buildkit/testwithprovenance:" + mode + var target string + if target == "" { + target = registry + "/buildkit/testwithprovenance:none" + } else { + target = registry + "/buildkit/testwithprovenance:" + mode + } + provReq := "" - if mode == "max" { - provReq = "mode=max" + if mode != "" { + provReq = "mode=" + mode } _, err = f.Solve(sb.Context(), c, client.SolveOpt{ LocalDirs: map[string]string{ @@ -129,7 +135,7 @@ RUN echo "ok" > /foo } else if isGateway { require.Equal(t, "gateway.v0", pred.Invocation.Parameters.Frontend) - if mode == "max" { + if mode == "max" || mode == "" { require.Equal(t, 3, len(args), "%v", args) require.True(t, pred.Metadata.Completeness.Parameters) @@ -144,7 +150,7 @@ RUN echo "ok" > /foo } else { require.Equal(t, "dockerfile.v0", pred.Invocation.Parameters.Frontend) - if mode == "max" { + if mode == "max" || mode == "" { require.Equal(t, 2, len(args)) require.True(t, pred.Metadata.Completeness.Parameters) @@ -193,7 +199,7 @@ RUN echo "ok" > /foo require.False(t, pred.Metadata.Reproducible) require.False(t, pred.Metadata.Completeness.Hermetic) - if mode == "max" { + if mode == "max" || mode == "" { require.Equal(t, 2, len(pred.Metadata.BuildKitMetadata.Layers)) require.NotNil(t, pred.Metadata.BuildKitMetadata.Source) require.Equal(t, "Dockerfile", pred.Metadata.BuildKitMetadata.Source.Infos[0].Filename) diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index 3e7ce4a1e1e2..d5448a18a96e 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -54,7 +54,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { reproducible = b } - var mode string + mode := "max" if v, ok := attrs["mode"]; ok { switch v { case "full": @@ -90,7 +90,8 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { var addLayers func() error - if mode != "max" { + switch mode { + case "min": args := make(map[string]string) for k, v := range pr.Invocation.Parameters.Args { if strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") { @@ -102,7 +103,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { pr.Invocation.Parameters.Args = args pr.Invocation.Parameters.Secrets = nil pr.Invocation.Parameters.SSH = nil - } else { + case "max": dgsts, err := provenance.AddBuildConfig(ctx, pr, res.Refs[p.ID]) if err != nil { return nil, err @@ -144,6 +145,8 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { return nil } + default: + return nil, errors.Errorf("invalid mode %q", mode) } res.AddAttestation(p.ID, result.Attestation{