diff --git a/cmd/guacone/cmd/known.go b/cmd/guacone/cmd/known.go index f31ba7e27a..157960f3de 100644 --- a/cmd/guacone/cmd/known.go +++ b/cmd/guacone/cmd/known.go @@ -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 { @@ -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 { @@ -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 diff --git a/internal/testing/testdata/testdata.go b/internal/testing/testdata/testdata.go index d811fc3eee..8ebe7fbd28 100644 --- a/internal/testing/testdata/testdata.go +++ b/internal/testing/testdata/testdata.go @@ -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, @@ -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", @@ -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{ diff --git a/pkg/ingestor/parser/slsa/parser_slsa.go b/pkg/ingestor/parser/slsa/parser_slsa.go index e6c79b01a1..2e1ebb1ad3 100644 --- a/pkg/ingestor/parser/slsa/parser_slsa.go +++ b/pkg/ingestor/parser/slsa/parser_slsa.go @@ -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 } @@ -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 } @@ -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 } @@ -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, @@ -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 } diff --git a/pkg/ingestor/parser/slsa/parser_slsa_test.go b/pkg/ingestor/parser/slsa/parser_slsa_test.go index fd077930c8..daf3e6b4c2 100644 --- a/pkg/ingestor/parser/slsa/parser_slsa_test.go +++ b/pkg/ingestor/parser/slsa/parser_slsa_test.go @@ -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" @@ -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) { @@ -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) + } + }) + } +}