Skip to content
Open
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
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.26.0
require (
cloud.google.com/go/storage v1.61.3
github.com/fsouza/fake-gcs-server v1.54.0
github.com/in-toto/in-toto-golang v0.10.0
github.com/secure-systems-lab/go-securesystemslib v0.10.0
github.com/spf13/cobra v1.10.2
go.uber.org/zap v1.27.1
Expand All @@ -27,7 +26,6 @@ require (
github.com/gorilla/mux v1.8.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pkg/xattr v0.4.12 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/spf13/pflag v1.0.10
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -918,8 +918,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20250628045327-2d64ad6b7ec5 h1:QCtizt3
github.com/ianlancetaylor/demangle v0.0.0-20250628045327-2d64ad6b7ec5/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E=
github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM=
github.com/in-toto/in-toto-golang v0.10.0 h1:+s2eZQSK3WmWfYV85qXVSBfqgawi/5L02MaqA4o/tpM=
github.com/in-toto/in-toto-golang v0.10.0/go.mod h1:wjT4RiyFlLWCmLUJjwB8oZcjaq7HA390aMJcD3xXgmg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
Expand Down Expand Up @@ -1328,8 +1326,6 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
Expand Down
70 changes: 48 additions & 22 deletions pkg/handler/processor/guesser/type_ite6.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,66 @@ import (
jsoniter "github.com/json-iterator/go"

"github.com/guacsec/guac/pkg/handler/processor"
attestationv1 "github.com/in-toto/attestation/go/v1"
"github.com/in-toto/in-toto-golang/in_toto"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

// ite6Statement supports unmarshalling both v0.1 ("_type", "predicateType")
// and v1 ("type", "predicate_type") in-toto statement formats.
type ite6Statement struct {
Comment thread
Abhishek9639 marked this conversation as resolved.
TypeV01 string `json:"_type"`
PredicateTypeV01 string `json:"predicateType"`
TypeV1 string `json:"type"`
PredicateTypeV1 string `json:"predicate_type"`
}

// getType returns the statement type from whichever format was used.
// in-toto v1 is the current standard; v0.1 is supported for backwards
// compatibility. A document that populates both _type and type is malformed.
func (s *ite6Statement) getType() string {
Comment thread
Abhishek9639 marked this conversation as resolved.
if s.TypeV1 != "" && s.TypeV01 != "" {
// Both fields set: reject the ambiguous/malformed document.
return ""
}
if s.TypeV1 != "" {
return s.TypeV1
}
return s.TypeV01
}

// getPredicateType returns the predicate type from whichever format was used.
// in-toto v1 is the current standard; v0.1 is supported for backwards
// compatibility. A document that populates both predicateType and predicate_type
// is malformed.
func (s *ite6Statement) getPredicateType() string {
if s.PredicateTypeV1 != "" && s.PredicateTypeV01 != "" {
// Both fields set: reject the ambiguous/malformed document.
return ""
}
if s.PredicateTypeV1 != "" {
return s.PredicateTypeV1
}
return s.PredicateTypeV01
}

type ite6TypeGuesser struct{}

func (_ *ite6TypeGuesser) GuessDocumentType(blob []byte, format processor.FormatType) processor.DocumentType {
var statement in_toto.Statement
var statement ite6Statement
if json.Unmarshal(blob, &statement) == nil && format == processor.FormatJSON {
if strings.HasPrefix(statement.Type, "https://in-toto.io/Statement") {
if strings.HasPrefix(statement.PredicateType, "https://slsa.dev/provenance") {
stmtType := statement.getType()
predicateType := statement.getPredicateType()
if strings.HasPrefix(stmtType, "https://in-toto.io/Statement") {
if strings.HasPrefix(predicateType, "https://slsa.dev/provenance") {
return processor.DocumentITE6SLSA
} else if strings.HasPrefix(statement.PredicateType, "https://crev.dev/in-toto-scheme") {
} else if strings.HasPrefix(predicateType, "https://crev.dev/in-toto-scheme") {
return processor.DocumentITE6Generic
} else if strings.HasPrefix(statement.PredicateType, "https://in-toto.io/attestation/certify/v0.1") {
} else if strings.HasPrefix(predicateType, "https://in-toto.io/attestation/certify/v0.1") {
return processor.DocumentITE6Generic
} else if strings.HasPrefix(statement.PredicateType, "https://in-toto.io/attestation/vulns/v0.1") ||
strings.HasPrefix(statement.PredicateType, "https://in-toto.io/attestation/vulns/v0.2") {
return processor.DocumentITE6Vul
} else if strings.HasPrefix(statement.PredicateType, "https://in-toto.io/attestation/clearlydefined/v0.1") {
return processor.DocumentITE6ClearlyDefined
}
return processor.DocumentITE6Generic
}
}
var attV1Statement attestationv1.Statement
if json.Unmarshal(blob, &attV1Statement) == nil && format == processor.FormatJSON {
if strings.HasPrefix(attV1Statement.Type, "https://in-toto.io/Statement") {
if strings.HasPrefix(attV1Statement.PredicateType, "https://in-toto.io/attestation/vulns/v0.1") ||
strings.HasPrefix(attV1Statement.PredicateType, "https://in-toto.io/attestation/vulns/v0.2") {
} else if strings.HasPrefix(predicateType, "https://in-toto.io/attestation/vulns/v0.1") ||
strings.HasPrefix(predicateType, "https://in-toto.io/attestation/vulns/v0.2") {
return processor.DocumentITE6Vul
} else if strings.HasPrefix(attV1Statement.PredicateType, "https://in-toto.io/attestation/clearlydefined/v0.1") {
} else if strings.HasPrefix(predicateType, "https://in-toto.io/attestation/clearlydefined/v0.1") {
return processor.DocumentITE6ClearlyDefined
}
return processor.DocumentITE6Generic
Expand Down
15 changes: 15 additions & 0 deletions pkg/handler/processor/guesser/type_ite6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,29 @@ func Test_Ite6TypeGuesser(t *testing.T) {
name: "valid ITE6 Document",
blob: []byte(`{"_type": "https://in-toto.io/Statement/v0.1"}`),
expected: processor.DocumentITE6Generic,
}, {
name: "valid ITE6 v1 Document",
blob: []byte(`{"type": "https://in-toto.io/Statement/v1"}`),
expected: processor.DocumentITE6Generic,
}, {
name: "valid SLSA ITE6 Document",
blob: []byte(`{"_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://slsa.dev/provenance/v0.2"}`),
expected: processor.DocumentITE6SLSA,
}, {
name: "valid SLSA ITE6 v1 Document",
blob: []byte(`{"type": "https://in-toto.io/Statement/v1", "predicate_type": "https://slsa.dev/provenance/v0.2"}`),
expected: processor.DocumentITE6SLSA,
}, {
name: "valid SLSA ITE6 Document with different versions",
blob: []byte(`{"_type": "https://in-toto.io/Statement/v1.1", "predicateType": "https://slsa.dev/provenance/v1.0"}`),
expected: processor.DocumentITE6SLSA,
}, {
// A document that populates both v0.1 and v1 type fields is malformed and
// must be rejected (returns DocumentUnknown) rather than silently
// preferring one format over the other.
name: "ambiguous ITE6 Document with both _type and type fields",
blob: []byte(`{"_type": "https://in-toto.io/Statement/v0.1", "type": "https://in-toto.io/Statement/v1", "predicateType": "https://slsa.dev/provenance/v0.2", "predicate_type": "https://slsa.dev/provenance/v0.2"}`),
expected: processor.DocumentUnknown,
}, {
name: "valid CREV ITE6 Document",
blob: testdata.ITE6CREVExample,
Expand Down
20 changes: 17 additions & 3 deletions pkg/handler/processor/ite6/ite6.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,21 @@ import (
jsoniter "github.com/json-iterator/go"

"github.com/guacsec/guac/pkg/handler/processor"
"github.com/in-toto/in-toto-golang/in_toto"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

// ite6Statement is used for unmarshalling in-toto statement headers.
// It handles both v0.1 (_type/predicateType) and v1 (type/predicate_type)
// in-toto statement formats. Exactly one set of fields should be populated;
// if neither is set after unmarshalling, the document is rejected with an error.
type ite6Statement struct {
Comment thread
Abhishek9639 marked this conversation as resolved.
TypeV01 string `json:"_type"`
PredicateTypeV01 string `json:"predicateType"`
TypeV1 string `json:"type"`
PredicateTypeV1 string `json:"predicate_type"`
}

type ITE6Processor struct {
}

Expand Down Expand Up @@ -53,10 +63,14 @@ func (e *ITE6Processor) Unpack(i *processor.Document) ([]*processor.Document, er
return nil, nil
}

func parseStatement(p []byte) (*in_toto.Statement, error) {
ps := in_toto.Statement{}
func parseStatement(p []byte) (*ite6Statement, error) {
ps := ite6Statement{}
if err := json.Unmarshal(p, &ps); err != nil {
return nil, err
}
// Reject documents where neither format's fields are populated.
if ps.TypeV01 == "" && ps.TypeV1 == "" {
return nil, fmt.Errorf("in-toto statement has neither v0.1 (_type) nor v1 (type) statement type field")
}
return &ps, nil
}
58 changes: 30 additions & 28 deletions pkg/ingestor/parser/slsa/parser_slsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ import (
"context"
"errors"
"fmt"

"strings"
"time"

jsoniter "github.com/json-iterator/go"

slsa1 "github.com/in-toto/attestation/go/predicates/provenance/v1"
attestationv1 "github.com/in-toto/attestation/go/v1"
scommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
slsa01 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.1"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/jeremywohl/flatten"
"google.golang.org/protobuf/encoding/protojson"

Expand All @@ -45,7 +41,13 @@ import (
// - a pkg or source depending on what is represented by the name/URI
// - An IsOccurrence input spec which will generate a predicate for each occurrence

const PredicateSLSAProvenancev1 = "https://slsa.dev/provenance/v1"
const (
PredicateSLSAProvenanceV01 = "https://slsa.dev/provenance/v0.1"
PredicateSLSAProvenanceV02 = "https://slsa.dev/provenance/v0.2"

// PredicateSLSAProvenanceV1 is the predicate type for SLSAv1.0 provenance.
PredicateSLSAProvenanceV1 = "https://slsa.dev/provenance/v1"
)

var ErrMetadataNil = errors.New("SLSA Metadata is nil")
var ErrBuilderNil = errors.New("SLSA Builder is nil")
Expand All @@ -61,8 +63,8 @@ type slsaEntity struct {
}

type slsaParser struct {
pred01 *slsa01.ProvenancePredicate
pred02 *slsa02.ProvenancePredicate
pred01 *ProvenancePredicateV01
pred02 *ProvenancePredicateV02
pred1 *slsa1.Provenance
smt *attestationv1.Statement
subjects []*slsaEntity
Expand Down Expand Up @@ -131,15 +133,15 @@ func (s *slsaParser) getSubject() error {

func (s *slsaParser) getMaterials() error {
switch s.smt.PredicateType {
case slsa01.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV01:
if err := s.getMaterials0(s.pred01.Materials); err != nil {
return err
}
case slsa02.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV02:
if err := s.getMaterials0(s.pred02.Materials); err != nil {
return err
}
case PredicateSLSAProvenancev1:
case PredicateSLSAProvenanceV1:
if s.pred1.BuildDefinition == nil {
return errors.New("SLSA1 buildDefinition is nil")
}
Expand Down Expand Up @@ -174,7 +176,7 @@ func (s *slsaParser) getMaterials1(rds []*attestationv1.ResourceDescriptor) erro
return nil
}

func (s *slsaParser) getMaterials0(materials []scommon.ProvenanceMaterial) error {
func (s *slsaParser) getMaterials0(materials []ProvenanceMaterial) error {
// append dependency nodes for the materials
for _, mat := range materials {
s.identifierStrings.UnclassifiedStrings = append(s.identifierStrings.UnclassifiedStrings, mat.URI)
Expand All @@ -187,7 +189,7 @@ func (s *slsaParser) getMaterials0(materials []scommon.ProvenanceMaterial) error
return nil
}

func getArtifacts(digests scommon.DigestSet) []*model.ArtifactInputSpec {
func getArtifacts(digests DigestSet) []*model.ArtifactInputSpec {
var artifacts []*model.ArtifactInputSpec
for alg, ds := range digests {
artifacts = append(artifacts, &model.ArtifactInputSpec{
Expand All @@ -198,7 +200,7 @@ func getArtifacts(digests scommon.DigestSet) []*model.ArtifactInputSpec {
return artifacts
}

func getSlsaEntity(name, uri string, digests scommon.DigestSet) (*slsaEntity, error) {
func getSlsaEntity(name, uri string, digests DigestSet) (*slsaEntity, error) {
artifacts := getArtifacts(digests)
slsa := &slsaEntity{
artifacts: artifacts,
Expand Down Expand Up @@ -231,7 +233,7 @@ func getSlsaEntity(name, uri string, digests scommon.DigestSet) (*slsaEntity, er
return nil, fmt.Errorf("%w unable to get Guac Generic Purl, this should not happen", err)
}

func fillSLSA01(inp *model.SLSAInputSpec, pred *slsa01.ProvenancePredicate) error {
func fillSLSA01(inp *model.SLSAInputSpec, pred *ProvenancePredicateV01) error {
inp.BuildType = pred.Recipe.Type

if pred.Metadata == nil {
Expand All @@ -247,7 +249,7 @@ func fillSLSA01(inp *model.SLSAInputSpec, pred *slsa01.ProvenancePredicate) erro
return nil
}

func fillSLSA02(inp *model.SLSAInputSpec, pred *slsa02.ProvenancePredicate) error {
func fillSLSA02(inp *model.SLSAInputSpec, pred *ProvenancePredicateV02) error {
inp.BuildType = pred.BuildType

if pred.Metadata == nil {
Expand All @@ -257,7 +259,7 @@ func fillSLSA02(inp *model.SLSAInputSpec, pred *slsa02.ProvenancePredicate) erro
inp.StartedOn = pred.Metadata.BuildStartedOn
}
if pred.Metadata.BuildFinishedOn != nil {
inp.FinishedOn = pred.Metadata.BuildStartedOn
inp.FinishedOn = pred.Metadata.BuildFinishedOn
}
return nil
}
Expand All @@ -272,7 +274,7 @@ func fillSLSA1(inp *model.SLSAInputSpec, pred *slsa1.Provenance) error {
inp.StartedOn = &startTimePB
}
if pred.RunDetails.Metadata.FinishedOn != nil {
finishTimePB := time.Unix(pred.RunDetails.Metadata.StartedOn.GetSeconds(), int64(pred.RunDetails.Metadata.StartedOn.GetNanos()))
finishTimePB := time.Unix(pred.RunDetails.Metadata.FinishedOn.GetSeconds(), int64(pred.RunDetails.Metadata.FinishedOn.GetNanos()))
inp.FinishedOn = &finishTimePB
}
return nil
Expand All @@ -286,21 +288,21 @@ func (s *slsaParser) getSLSA() error {
var data []byte
var err error
switch s.smt.PredicateType {
case slsa01.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV01:
if err := fillSLSA01(inp, s.pred01); err != nil {
return fmt.Errorf("could not fill SLSA01: %w", err)
}
if data, err = json.Marshal(s.pred01); err != nil {
return fmt.Errorf("could not marshal SLSA01: %w", err)
}
case slsa02.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV02:
if err := fillSLSA02(inp, s.pred02); err != nil {
return fmt.Errorf("could not fill SLSA02: %w", err)
}
if data, err = json.Marshal(s.pred02); err != nil {
return fmt.Errorf("could not marshal SLSA02: %w", err)
}
case PredicateSLSAProvenancev1:
case PredicateSLSAProvenanceV1:
if err := fillSLSA1(inp, s.pred1); err != nil {
return fmt.Errorf("could not fill SLSA1: %w", err)
}
Expand Down Expand Up @@ -335,11 +337,11 @@ func (s *slsaParser) getSLSA() error {
func (s *slsaParser) getBuilder() error {
s.builder = &model.BuilderInputSpec{}
switch s.smt.PredicateType {
case slsa01.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV01:
s.builder.Uri = s.pred01.Builder.ID
case slsa02.PredicateSLSAProvenance:
case PredicateSLSAProvenanceV02:
s.builder.Uri = s.pred02.Builder.ID
case PredicateSLSAProvenancev1:
case PredicateSLSAProvenanceV1:
if s.pred1.RunDetails == nil || s.pred1.RunDetails.Builder == nil {
return ErrBuilderNil
}
Expand All @@ -360,17 +362,17 @@ func (s *slsaParser) parseSlsaPredicate(p []byte) error {
}

switch s.smt.PredicateType {
case slsa01.PredicateSLSAProvenance:
s.pred01 = &slsa01.ProvenancePredicate{}
case PredicateSLSAProvenanceV01:
s.pred01 = &ProvenancePredicateV01{}
if err := json.Unmarshal(predBytes, s.pred01); err != nil {
return fmt.Errorf("Could not unmarshal v0.1 SLSA provenance statement : %w", err)
}
case slsa02.PredicateSLSAProvenance:
s.pred02 = &slsa02.ProvenancePredicate{}
case PredicateSLSAProvenanceV02:
s.pred02 = &ProvenancePredicateV02{}
if err := json.Unmarshal(predBytes, s.pred02); err != nil {
return fmt.Errorf("Could not unmarshal v0.2 SLSA provenance statement : %w", err)
}
case PredicateSLSAProvenancev1:
case PredicateSLSAProvenanceV1:
s.pred1 = &slsa1.Provenance{}
if err := protojson.Unmarshal(predBytes, s.pred1); err != nil {
return fmt.Errorf("Could not unmarshal v1.0 SLSA provenance statement : %w", err)
Expand Down
Loading
Loading