Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions exporter/attestation/unbundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -20,6 +21,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))

Expand All @@ -28,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/") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check .. in the URL path?

I assume not needed, but posting this for confirmation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we do - my understanding is that end-users should treat as essentially opaque strings and not attempt to resolve them: https://github.com/in-toto/attestation/blob/main/spec/field_types.md#TypeURI.

The only reason we do the strings.HasPrefix here instead of comparing directly against the strings in the in-toto library is to match against different versions (including unreleased ones).

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 {
Expand All @@ -52,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
Expand All @@ -65,10 +81,9 @@ 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 {
return nil, err
}

if err := Validate(joined); err != nil {
return nil, err
}
return joined, nil
}
Expand Down Expand Up @@ -117,6 +132,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{
Expand All @@ -128,8 +144,17 @@ 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 == "" {
if att.Kind != gatewaypb.AttestationKindBundle && att.Path == "" {
return errors.New("attestation does not have set path")
}
if att.Ref == nil && att.ContentFunc == nil {
Expand Down
3 changes: 2 additions & 1 deletion exporter/containerimage/attestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion frontend/attestations/sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 14 additions & 7 deletions frontend/gateway/forwarder/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion solver/llbsolver/proc/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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{
Expand Down
6 changes: 3 additions & 3 deletions solver/result/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down