Unify root/material unknown resolution with recursive Input traversal. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
227 lines
6.3 KiB
Go
227 lines
6.3 KiB
Go
package policy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
|
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
|
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
|
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const predicateTypeAnnotation = "in-toto.io/predicate-type"
|
|
|
|
var resolveProvenanceAttestations = []string{
|
|
slsa02.PredicateSLSAProvenance,
|
|
slsa1.PredicateSLSAProvenance,
|
|
}
|
|
|
|
type inTotoStatement struct {
|
|
PredicateType string `json:"predicateType"`
|
|
Predicate json.RawMessage `json:"predicate"`
|
|
}
|
|
|
|
func parseProvenance(ac *gwpb.AttestationChain, logf func(logrus.Level, string)) (*ImageProvenance, error) {
|
|
if ac == nil || len(ac.Blobs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
for _, b := range ac.Blobs {
|
|
if b == nil || b.Descriptor_ == nil || len(b.Data) == 0 {
|
|
continue
|
|
}
|
|
pt := b.Descriptor_.Annotations[predicateTypeAnnotation]
|
|
if pt == "" {
|
|
continue
|
|
}
|
|
if !slices.Contains(resolveProvenanceAttestations, pt) {
|
|
continue
|
|
}
|
|
prv, err := parseProvenanceBlob(b.Data, pt, logf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if prv != nil {
|
|
return prv, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func parseProvenanceBlob(dt []byte, pt string, logf func(logrus.Level, string)) (*ImageProvenance, error) {
|
|
var stmt inTotoStatement
|
|
if err := json.Unmarshal(dt, &stmt); err != nil {
|
|
return nil, nil
|
|
}
|
|
if len(stmt.Predicate) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
predicateType := stmt.PredicateType
|
|
if predicateType == "" {
|
|
predicateType = pt
|
|
}
|
|
switch predicateType {
|
|
case slsa1.PredicateSLSAProvenance:
|
|
return parseSLSA1Provenance(stmt.Predicate, logf)
|
|
case slsa02.PredicateSLSAProvenance:
|
|
return parseSLSA02Provenance(stmt.Predicate, logf)
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func parseSLSA1Provenance(dt []byte, logf func(logrus.Level, string)) (*ImageProvenance, error) {
|
|
var pred provenancetypes.ProvenancePredicateSLSA1
|
|
if err := json.Unmarshal(dt, &pred); err != nil {
|
|
return nil, nil
|
|
}
|
|
if pred.BuildDefinition.BuildType == "" && pred.RunDetails.Builder.ID == "" {
|
|
return nil, nil
|
|
}
|
|
prv := &ImageProvenance{
|
|
PredicateType: slsa1.PredicateSLSAProvenance,
|
|
BuildType: pred.BuildDefinition.BuildType,
|
|
BuilderID: pred.RunDetails.Builder.ID,
|
|
ConfigSource: &ImageProvenanceConfigSource{
|
|
URI: pred.BuildDefinition.ExternalParameters.ConfigSource.URI,
|
|
Digest: pred.BuildDefinition.ExternalParameters.ConfigSource.Digest,
|
|
Path: pred.BuildDefinition.ExternalParameters.ConfigSource.Path,
|
|
},
|
|
Frontend: pred.BuildDefinition.ExternalParameters.Request.Frontend,
|
|
BuildArgs: extractBuildArgs(pred.BuildDefinition.ExternalParameters.Request.Args),
|
|
RawArgs: pred.BuildDefinition.ExternalParameters.Request.Args,
|
|
}
|
|
prv.materialsRaw = rawMaterialsFromSLSA1(pred.BuildDefinition.ResolvedDependencies, logf)
|
|
|
|
if md := pred.RunDetails.Metadata; md != nil {
|
|
prv.InvocationID = md.InvocationID
|
|
prv.StartedOn = formatProvenanceTime(md.StartedOn)
|
|
prv.FinishedOn = formatProvenanceTime(md.FinishedOn)
|
|
prv.Reproducible = boolPtr(md.Reproducible)
|
|
prv.Hermetic = boolPtr(md.Hermetic)
|
|
prv.Completeness = &ImageProvenanceCompleteness{
|
|
Parameters: boolPtr(md.Completeness.Request),
|
|
Materials: boolPtr(md.Completeness.ResolvedDependencies),
|
|
}
|
|
}
|
|
|
|
return prv, nil
|
|
}
|
|
|
|
func parseSLSA02Provenance(dt []byte, logf func(logrus.Level, string)) (*ImageProvenance, error) {
|
|
var pred provenancetypes.ProvenancePredicateSLSA02
|
|
if err := json.Unmarshal(dt, &pred); err != nil {
|
|
return nil, nil
|
|
}
|
|
if pred.BuildType == "" && pred.Builder.ID == "" {
|
|
return nil, nil
|
|
}
|
|
prv := &ImageProvenance{
|
|
PredicateType: slsa02.PredicateSLSAProvenance,
|
|
BuildType: pred.BuildType,
|
|
BuilderID: pred.Builder.ID,
|
|
ConfigSource: &ImageProvenanceConfigSource{
|
|
URI: pred.Invocation.ConfigSource.URI,
|
|
Digest: pred.Invocation.ConfigSource.Digest,
|
|
Path: pred.Invocation.ConfigSource.EntryPoint,
|
|
},
|
|
Frontend: pred.Invocation.Parameters.Frontend,
|
|
BuildArgs: extractBuildArgs(pred.Invocation.Parameters.Args),
|
|
RawArgs: pred.Invocation.Parameters.Args,
|
|
}
|
|
prv.materialsRaw = rawMaterialsFromSLSA02(pred.Materials, logf)
|
|
|
|
if md := pred.Metadata; md != nil {
|
|
prv.InvocationID = md.BuildInvocationID
|
|
prv.StartedOn = formatProvenanceTime(md.BuildStartedOn)
|
|
prv.FinishedOn = formatProvenanceTime(md.BuildFinishedOn)
|
|
prv.Reproducible = boolPtr(md.Reproducible)
|
|
prv.Hermetic = boolPtr(md.Hermetic)
|
|
prv.Completeness = &ImageProvenanceCompleteness{
|
|
Parameters: boolPtr(md.Completeness.Parameters),
|
|
Environment: boolPtr(md.Completeness.Environment),
|
|
Materials: boolPtr(md.Completeness.Materials),
|
|
}
|
|
}
|
|
|
|
return prv, nil
|
|
}
|
|
|
|
func rawMaterialsFromSLSA1(materials []slsa1.ResourceDescriptor, logf func(logrus.Level, string)) []slsa1.ResourceDescriptor {
|
|
if len(materials) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]slsa1.ResourceDescriptor, 0, len(materials))
|
|
for _, m := range materials {
|
|
rd := slsa1.ResourceDescriptor{
|
|
URI: m.URI,
|
|
Digest: maps.Clone(m.Digest),
|
|
}
|
|
if _, _, err := parseSLSAMaterial(rd); err != nil {
|
|
if logf != nil {
|
|
logf(logrus.WarnLevel, fmt.Sprintf("skipping unsupported provenance material %q: %v", m.URI, err))
|
|
}
|
|
continue
|
|
}
|
|
out = append(out, rd)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func rawMaterialsFromSLSA02(materials []slsa02.ProvenanceMaterial, logf func(logrus.Level, string)) []slsa1.ResourceDescriptor {
|
|
if len(materials) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]slsa1.ResourceDescriptor, 0, len(materials))
|
|
for _, m := range materials {
|
|
rd := slsa1.ResourceDescriptor{
|
|
URI: m.URI,
|
|
Digest: maps.Clone(m.Digest),
|
|
}
|
|
if _, _, err := parseSLSAMaterial(rd); err != nil {
|
|
if logf != nil {
|
|
logf(logrus.WarnLevel, fmt.Sprintf("skipping unsupported provenance material %q: %v", m.URI, err))
|
|
}
|
|
continue
|
|
}
|
|
out = append(out, rd)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func boolPtr(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func formatProvenanceTime(t *time.Time) string {
|
|
if t == nil {
|
|
return ""
|
|
}
|
|
return t.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
func extractBuildArgs(args map[string]string) map[string]string {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
const prefix = "build-arg:"
|
|
out := make(map[string]string)
|
|
for k, v := range args {
|
|
if name, ok := strings.CutPrefix(k, prefix); ok && name != "" {
|
|
out[name] = v
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|