Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated query known and slsa parser #2018

Merged
merged 6 commits into from
Jul 17, 2024
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
49 changes: 35 additions & 14 deletions cmd/guacone/cmd/known.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,24 @@ func getOutputBasedOnNode(ctx context.Context, gqlclient graphql.Client, collect
tableRows = append(tableRows, table.Row{vexLinkStr, vex.Id, "Vex Status: " + vex.Status})
}
case hasSBOMStr:
for _, sbom := range collectedNeighbors.hasSBOMs {
tableRows = append(tableRows, table.Row{hasSBOMStr, sbom.Id, "SBOM Download Location: " + sbom.DownloadLocation})
if len(collectedNeighbors.hasSBOMs) > 0 {
for _, sbom := range collectedNeighbors.hasSBOMs {
tableRows = append(tableRows, table.Row{hasSBOMStr, sbom.Id, "SBOM Download Location: " + sbom.DownloadLocation})
}
} else {
// if there is an isOccurrence, check to see if there are sbom associated with it
for _, occurrence := range collectedNeighbors.occurrences {
neighborResponseHasSBOM, err := getAssociatedArtifact(ctx, gqlclient, occurrence, model.EdgeArtifactHasSbom)
if err != nil {
logger.Debugf("error querying neighbors: %v", err)
} else {
for _, neighborHasSBOM := range neighborResponseHasSBOM.Neighbors {
if hasSBOM, ok := neighborHasSBOM.(*model.NeighborsNeighborsHasSBOM); ok {
tableRows = append(tableRows, table.Row{hasSBOMStr, hasSBOM.Id, "SBOM Download Location: " + hasSBOM.DownloadLocation})
}
}
}
}
}
case hasSLSAStr:
if len(collectedNeighbors.hasSLSAs) > 0 {
Expand All @@ -372,18 +388,7 @@ func getOutputBasedOnNode(ctx context.Context, gqlclient graphql.Client, collect
} else {
// if there is an isOccurrence, check to see if there are slsa attestation associated with it
for _, occurrence := range collectedNeighbors.occurrences {
artifactFilter := &model.ArtifactSpec{
Algorithm: &occurrence.Artifact.Algorithm,
Digest: &occurrence.Artifact.Digest,
}
artifactResponse, err := model.Artifacts(ctx, gqlclient, *artifactFilter)
if err != nil {
logger.Debugf("error querying for artifacts: %v", err)
}
if len(artifactResponse.Artifacts) != 1 {
logger.Debugf("failed to located artifacts based on (algorithm:digest)")
}
neighborResponseHasSLSA, err := model.Neighbors(ctx, gqlclient, artifactResponse.Artifacts[0].Id, []model.Edge{model.EdgeArtifactHasSlsa})
neighborResponseHasSLSA, err := getAssociatedArtifact(ctx, gqlclient, occurrence, model.EdgeArtifactHasSlsa)
if err != nil {
logger.Debugf("error querying neighbors: %v", err)
} else {
Expand Down Expand Up @@ -449,6 +454,22 @@ func getOutputBasedOnNode(ctx context.Context, gqlclient graphql.Client, collect
return tableRows
}

func getAssociatedArtifact(ctx context.Context, gqlclient graphql.Client, occurrence *model.NeighborsNeighborsIsOccurrence, edge model.Edge) (*model.NeighborsResponse, error) {
logger := logging.FromContext(ctx)
artifactFilter := &model.ArtifactSpec{
Algorithm: &occurrence.Artifact.Algorithm,
Digest: &occurrence.Artifact.Digest,
}
artifactResponse, err := model.Artifacts(ctx, gqlclient, *artifactFilter)
if err != nil {
logger.Debugf("error querying for artifacts: %v", err)
}
if len(artifactResponse.Artifacts) != 1 {
logger.Debugf("failed to located artifacts based on (algorithm:digest)")
}
return model.Neighbors(ctx, gqlclient, artifactResponse.Artifacts[0].Id, []model.Edge{edge})
}

func validateQueryKnownFlags(graphqlEndpoint, headerFile string, args []string) (queryKnownOptions, error) {
var opts queryKnownOptions
opts.graphqlEndpoint = graphqlEndpoint
Expand Down
110 changes: 110 additions & 0 deletions internal/testing/testdata/testdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,44 @@ var (
]
}
`

ite6SLSA1_2 = `
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "sigstore",
"uri": "pkg:npm/sigstore/sigstore-js@4.2.0",
"digest": {
"sha1": "428601801d1f5d105351a403f58c38269de93f680"
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/npm/cli/gha/v2",
"resolved_dependencies": [
{
"uri": "pkg:npm/sigstore/segs@1.2.0",
"digest": {
"sha1": "5b8c0801d1f5d105351a403f58c38269de93f680"
}
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/actions/runner"
},
"metadata": {
"invocationId": "b6186090-c8ff-4f91-97cf-7a3b47699e57",
"startedOn": "2022-05-24T12:13:35.054695403Z"
}
}
}
}`

Ite6SLSA1Doc = processor.Document{
Blob: []byte(ite6SLSA1),
Type: processor.DocumentITE6SLSA,
Expand All @@ -393,6 +431,16 @@ var (
},
}

Ite6SLSA1Doc_2 = processor.Document{
Blob: []byte(ite6SLSA1_2),
Type: processor.DocumentITE6SLSA,
Format: processor.FormatJSON,
SourceInformation: processor.SourceInformation{
Collector: "TestCollector",
Source: "TestSource",
},
}

b64ITE6SLSA = base64.StdEncoding.EncodeToString([]byte(ite6SLSA02))
Ite6Payload, _ = json.Marshal(dsse.Envelope{
PayloadType: "https://in-toto.io/Statement/v0.1",
Expand Down Expand Up @@ -574,6 +622,68 @@ var (
},
}

slsa1time_2, _ = time.Parse(time.RFC3339, "2022-05-24T12:13:35.054695403Z")
SlsaPreds1_2 = assembler.IngestPredicates{
IsOccurrence: []assembler.IsOccurrenceIngest{
{
Pkg: &model.PkgInputSpec{
Type: "npm",
Namespace: ptrfrom.String("sigstore"),
Name: "segs",
Version: ptrfrom.String("1.2.0"),
Subpath: ptrfrom.String(""),
},
Artifact: &model.ArtifactInputSpec{
Algorithm: "sha1",
Digest: "5b8c0801d1f5d105351a403f58c38269de93f680",
},
IsOccurrence: &slsaIsOccurrence,
},
{
Pkg: &model.PkgInputSpec{
Type: "npm",
Namespace: ptrfrom.String("sigstore"),
Name: "sigstore-js",
Version: ptrfrom.String("4.2.0"),
Subpath: ptrfrom.String(""),
},
Artifact: &model.ArtifactInputSpec{
Algorithm: "sha1",
Digest: "428601801d1f5d105351a403f58c38269de93f680",
},
IsOccurrence: &slsaIsOccurrence,
},
},
HasSlsa: []assembler.HasSlsaIngest{
{
Artifact: &model.ArtifactInputSpec{
Algorithm: "sha1",
Digest: "428601801d1f5d105351a403f58c38269de93f680",
},
Builder: &model.BuilderInputSpec{
Uri: "https://github.com/actions/runner",
},
Materials: []model.ArtifactInputSpec{{
Algorithm: "sha1",
Digest: "5b8c0801d1f5d105351a403f58c38269de93f680",
}},
HasSlsa: &model.SLSAInputSpec{
BuildType: "https://github.com/npm/cli/gha/v2",
SlsaVersion: "https://slsa.dev/provenance/v1",
StartedOn: &slsa1time_2,
SlsaPredicate: []model.SLSAPredicateInputSpec{
{Key: "slsa.buildDefinition.buildType", Value: "https://github.com/npm/cli/gha/v2"},
{Key: "slsa.buildDefinition.resolvedDependencies.0.digest.sha1", Value: "5b8c0801d1f5d105351a403f58c38269de93f680"},
{Key: "slsa.buildDefinition.resolvedDependencies.0.uri", Value: "pkg:npm/sigstore/segs@1.2.0"},
{Key: "slsa.runDetails.builder.id", Value: "https://github.com/actions/runner"},
{Key: "slsa.runDetails.metadata.invocationId", Value: "b6186090-c8ff-4f91-97cf-7a3b47699e57"},
{Key: "slsa.runDetails.metadata.startedOn", Value: "2022-05-24T12:13:35.054695403Z"},
},
},
},
},
}

// TODO: needs to be resolved by https://github.com/guacsec/guac/issues/75
Ident = []common.TrustInformation{}
// Ident = assembler.IdentityNode{
Expand Down
14 changes: 9 additions & 5 deletions pkg/ingestor/parser/slsa/parser_slsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (s *slsaParser) getSubject() error {
// append artifact node for the subjects
for _, sub := range s.smt.Subject {
s.identifierStrings.UnclassifiedStrings = append(s.identifierStrings.UnclassifiedStrings, sub.Name)
se, err := getSlsaEntity(sub.Name, sub.Digest)
se, err := getSlsaEntity(sub.Name, sub.Uri, sub.Digest)
if err != nil {
return err
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func (s *slsaParser) getMaterials1(rds []*attestationv1.ResourceDescriptor) erro
}
// Digest(s) and URI are set, create IsOccurrence between them.
s.identifierStrings.UnclassifiedStrings = append(s.identifierStrings.UnclassifiedStrings, rd.Uri)
se, err := getSlsaEntity(rd.Uri, rd.Digest)
se, err := getSlsaEntity(rd.Name, rd.Uri, rd.Digest)
if err != nil {
return err
}
Expand All @@ -162,7 +162,7 @@ func (s *slsaParser) getMaterials0(materials []scommon.ProvenanceMaterial) error
// append dependency nodes for the materials
for _, mat := range materials {
s.identifierStrings.UnclassifiedStrings = append(s.identifierStrings.UnclassifiedStrings, mat.URI)
se, err := getSlsaEntity(mat.URI, mat.Digest)
se, err := getSlsaEntity("", mat.URI, mat.Digest)
if err != nil {
return err
}
Expand All @@ -182,7 +182,7 @@ func getArtifacts(digests scommon.DigestSet) []*model.ArtifactInputSpec {
return artifacts
}

func getSlsaEntity(name string, digests scommon.DigestSet) (*slsaEntity, error) {
func getSlsaEntity(name, uri string, digests scommon.DigestSet) (*slsaEntity, error) {
artifacts := getArtifacts(digests)
slsa := &slsaEntity{
artifacts: artifacts,
Expand All @@ -191,7 +191,11 @@ func getSlsaEntity(name string, digests scommon.DigestSet) (*slsaEntity, error)
},
}

if pkg, err := helpers.PurlToPkg(name); err == nil {
if name == "" {
name = uri
}

if pkg, err := helpers.PurlToPkg(uri); err == nil {
slsa.pkg = pkg
return slsa, nil
}
Expand Down
110 changes: 109 additions & 1 deletion pkg/ingestor/parser/slsa/parser_slsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ package slsa

import (
"context"
"reflect"
"testing"
"time"

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"

"github.com/in-toto/in-toto-golang/in_toto"
Expand Down Expand Up @@ -47,11 +49,17 @@ func Test_slsaParser(t *testing.T) {
wantErr: false,
},
{
name: "testing v0.1",
name: "testing v1",
doc: &testdata.Ite6SLSA1Doc,
wantPredicates: &testdata.SlsaPreds1,
wantErr: false,
},
{
name: "testing v1-2",
doc: &testdata.Ite6SLSA1Doc_2,
wantPredicates: &testdata.SlsaPreds1_2,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -137,3 +145,103 @@ func Test_fillSLSA01(t *testing.T) {
})
}
}

func Test_getSlsaEntity(t *testing.T) {
namespace := "sigstore"
genericNamespace := "generic"
version := "4.2.0"
emptyString := ""
tests := []struct {
testname string
uri string
name string
digest scommon.DigestSet
expected *slsaEntity
wantErr bool
}{
{
testname: "with uri and digest",
uri: "pkg:npm/sigstore/sigstore-js@4.2.0",
digest: scommon.DigestSet{
"sha1": "428601801d1f5d105351a403f58c38269de93f680",
},
expected: &slsaEntity{
artifacts: []*model.ArtifactInputSpec{
{
Algorithm: "sha1",
Digest: "428601801d1f5d105351a403f58c38269de93f680",
},
},
pkg: &model.PkgInputSpec{
Type: "npm",
Namespace: &namespace,
Name: "sigstore-js",
Version: &version,
Subpath: &emptyString,
},
occurence: &model.IsOccurrenceInputSpec{
Justification: "from SLSA definition of checksums for subject/materials",
},
},
wantErr: false,
},
{
testname: "with name and digest",
name: "sigstore",
digest: scommon.DigestSet{
"sha1": "428601801d1f5d105351a403f58c38269de93f680",
},
expected: &slsaEntity{
artifacts: []*model.ArtifactInputSpec{
{
Algorithm: "sha1",
Digest: "428601801d1f5d105351a403f58c38269de93f680",
},
},
pkg: &model.PkgInputSpec{
Type: "guac",
Namespace: &genericNamespace,
Name: "sigstore",
Subpath: &emptyString,
Version: &emptyString,
},
occurence: &model.IsOccurrenceInputSpec{
Justification: "from SLSA definition of checksums for subject/materials",
},
},
wantErr: false,
},
{
testname: "without name and uri",
digest: scommon.DigestSet{
"sha1": "428601801d1f5d105351a403f58c38269de93f680",
},
wantErr: true,
},
}

for _, test := range tests {
t.Run(test.testname, func(t *testing.T) {
s, err := getSlsaEntity(test.name, test.uri, test.digest)
if (err != nil) != test.wantErr {
t.Errorf("slsa.Parse() error is not as expected. Expected: %v, Got: %v", err, test.wantErr)
return
}
if err != nil {
return
}
if !reflect.DeepEqual(s.pkg, test.expected.pkg) {
t.Errorf("getSlsaEntity() package is not as expected. Expected: %v, Got: %v", *s.pkg.Version, *test.expected.pkg.Version)
}
if !reflect.DeepEqual(s.source, test.expected.source) {
t.Errorf("getSlsaEntity() source is not as expected. Expected: %v, Got: %v", s.source, test.expected.source)
}
if !reflect.DeepEqual(s.occurence, test.expected.occurence) {
t.Errorf("getSlsaEntity() occurence is not as expected. Expected: %v, Got: %v", s.occurence, test.expected.occurence)
}
if !reflect.DeepEqual(s.artifacts, test.expected.artifacts) {
t.Errorf("getSlsaEntity() artifact is not as expected. Expected: %v, Got: %v", s.artifacts, test.expected.artifacts)
}
})
}
}
Loading