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/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/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 1a60ca2b11ed..d5448a18a96e 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -54,11 +54,9 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { reproducible = b } - var mode string + mode := "max" if v, ok := attrs["mode"]; ok { switch v { - case "disabled", "none": - return res, nil case "full": mode = "max" case "max", "min": @@ -68,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 { @@ -87,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:") { @@ -99,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 @@ -141,10 +145,16 @@ 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{ 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 81e8a09a5a99..e23e88ab3d18 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -5,9 +5,21 @@ import ( digest "github.com/opencontainers/go-digest" ) +const ( + AttestationReasonKey = "reason" + AttestationInlineOnlyKey = "inline-only" +) + +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)