Merge pull request #2194 from LaurentGoderre/sbom-dsse

Add support for DSSE envelope for attestation in imagetools
This commit is contained in:
Tõnis Tiigi 2024-02-28 20:08:07 -08:00 committed by GitHub
commit e273a53c88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,7 +4,9 @@ package imagetools
import ( import (
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"regexp"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -21,6 +23,12 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
const (
inTotoGenericMime = "application/vnd.in-toto+json"
inTotoSPDXDSSEMime = "application/vnd.in-toto.spdx+dsse"
inTotoProvenanceDSSEMime = "application/vnd.in-toto.provenance+dsse"
)
var ( var (
annotationReferences = []string{ annotationReferences = []string{
"com.docker.reference.digest", "com.docker.reference.digest",
@ -274,7 +282,7 @@ type sbomStub struct {
} }
func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") ctx = withIntotoMediaTypes(ctx)
as.deferredSbom = func() (*sbomStub, error) { as.deferredSbom = func() (*sbomStub, error) {
var sbom *sbomStub var sbom *sbomStub
for _, dgst := range refs { for _, dgst := range refs {
@ -283,7 +291,8 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
return nil, errors.Errorf("referenced image %s not found", dgst) return nil, errors.Errorf("referenced image %s not found", dgst)
} }
for _, layer := range mfst.manifest.Layers { for _, layer := range mfst.manifest.Layers {
if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) &&
layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" {
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
if err != nil { if err != nil {
return nil, err return nil, err
@ -292,6 +301,12 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
if err != nil { if err != nil {
return nil, err return nil, err
} }
dt, err = decodeDSSE(dt, layer.MediaType)
if err != nil {
return nil, err
}
var spdx struct { var spdx struct {
Predicate interface{} `json:"predicate"` Predicate interface{} `json:"predicate"`
} }
@ -318,7 +333,7 @@ type provenanceStub struct {
} }
func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error {
ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") ctx = withIntotoMediaTypes(ctx)
as.deferredProvenance = func() (*provenanceStub, error) { as.deferredProvenance = func() (*provenanceStub, error) {
var provenance *provenanceStub var provenance *provenanceStub
for _, dgst := range refs { for _, dgst := range refs {
@ -327,7 +342,8 @@ func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r
return nil, errors.Errorf("referenced image %s not found", dgst) return nil, errors.Errorf("referenced image %s not found", dgst)
} }
for _, layer := range mfst.manifest.Layers { for _, layer := range mfst.manifest.Layers {
if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { if (layer.MediaType == inTotoGenericMime || isInTotoDSSE(layer.MediaType)) &&
strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
if err != nil { if err != nil {
return nil, err return nil, err
@ -336,6 +352,12 @@ func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r
if err != nil { if err != nil {
return nil, err return nil, err
} }
dt, err = decodeDSSE(dt, layer.MediaType)
if err != nil {
return nil, err
}
var slsa struct { var slsa struct {
Predicate interface{} `json:"predicate"` Predicate interface{} `json:"predicate"`
} }
@ -415,3 +437,36 @@ func (r *result) SBOM() (map[string]sbomStub, error) {
} }
return res, nil return res, nil
} }
func isInTotoDSSE(mime string) bool {
isDSSE, _ := regexp.MatchString("application/vnd\\.in-toto\\..*\\+dsse", mime)
return isDSSE
}
func decodeDSSE(dt []byte, mime string) ([]byte, error) {
if isInTotoDSSE(mime) {
var dsse struct {
Payload string `json:"payload"`
}
if err := json.Unmarshal(dt, &dsse); err != nil {
return nil, err
}
decoded, err := base64.StdEncoding.DecodeString(dsse.Payload)
if err != nil {
return nil, err
}
dt = decoded
}
return dt, nil
}
func withIntotoMediaTypes(ctx context.Context) context.Context {
for _, mime := range []string{inTotoGenericMime, inTotoSPDXDSSEMime, inTotoProvenanceDSSEMime} {
ctx = remotes.WithMediaTypeKeyPrefix(ctx, mime, "intoto")
}
return ctx
}