diff --git a/cmd/guacone/cmd/files.go b/cmd/guacone/cmd/files.go index 320ede6ed3..9b561cde09 100644 --- a/cmd/guacone/cmd/files.go +++ b/cmd/guacone/cmd/files.go @@ -83,7 +83,7 @@ var exampleCmd = &cobra.Command{ logger.Errorf("error: %v", err) os.Exit(1) } - ingestorFunc, err := getIngestor() + ingestorFunc, err := getIngestor(ctx) if err != nil { logger.Errorf("error: %v", err) os.Exit(1) @@ -151,9 +151,9 @@ func getProcessor(ctx context.Context) (func(*processor.Document) (processor.Doc return process.Process(ctx, d) }, nil } -func getIngestor() (func(processor.DocumentTree) ([]assembler.Graph, error), error) { +func getIngestor(ctx context.Context) (func(processor.DocumentTree) ([]assembler.Graph, error), error) { return func(doc processor.DocumentTree) ([]assembler.Graph, error) { - inputs, err := parser.ParseDocumentTree(doc) + inputs, err := parser.ParseDocumentTree(ctx, doc) if err != nil { return nil, err } diff --git a/cmd/ingest/cmd/example.go b/cmd/ingest/cmd/example.go index bf75570b39..c190f7d7ac 100644 --- a/cmd/ingest/cmd/example.go +++ b/cmd/ingest/cmd/example.go @@ -79,7 +79,7 @@ var exampleCmd = &cobra.Command{ Edges: []assembler.GuacEdge{}, } for _, doc := range docs { - inputs, err := parser.ParseDocumentTree(doc) + inputs, err := parser.ParseDocumentTree(ctx, doc) if err != nil { logger.Errorf("unable to parse document: %v", err) os.Exit(1) diff --git a/go.mod b/go.mod index 55ffd58793..978c8307f0 100644 --- a/go.mod +++ b/go.mod @@ -66,5 +66,5 @@ require ( require ( github.com/sigstore/sigstore v1.4.2 - github.com/spdx/tools-golang v0.3.1-0.20220818163346-5eb9315c0c55 + github.com/spdx/tools-golang v0.3.1-0.20221003161519-fb7fe8874d01 ) diff --git a/go.sum b/go.sum index 92ecbea4a9..6a82221b32 100644 --- a/go.sum +++ b/go.sum @@ -285,8 +285,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.3.1-0.20220818163346-5eb9315c0c55 h1:vVdz7zz4iQvAyUYbjGvd35FMyQEoFjV3BvpnzzBKXZc= -github.com/spdx/tools-golang v0.3.1-0.20220818163346-5eb9315c0c55/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= +github.com/spdx/tools-golang v0.3.1-0.20221003161519-fb7fe8874d01 h1:HSwhIjCVoxeYkVPkUNH1l0ej8WBLgl7pNYz17vFOp/g= +github.com/spdx/tools-golang v0.3.1-0.20221003161519-fb7fe8874d01/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/internal/testing/ingestor/testdata/testdata.go b/internal/testing/ingestor/testdata/testdata.go index d74d0f9662..1d36686372 100644 --- a/internal/testing/ingestor/testdata/testdata.go +++ b/internal/testing/ingestor/testdata/testdata.go @@ -18,6 +18,7 @@ package testdata import ( "encoding/base64" "encoding/json" + "reflect" "github.com/guacsec/guac/internal/testing/ingestor/keyutil" "github.com/guacsec/guac/pkg/assembler" @@ -28,6 +29,8 @@ import ( ) var ( + // DSSE/SLSA Testdata + // Taken from: https://slsa.dev/provenance/v0.1#example ite6SLSA = ` { @@ -151,6 +154,96 @@ var ( ArtifactDependency: mat2, }, } + + // SDPX Testdata + + topLevelPack = assembler.PackageNode{ + Name: "gcr.io/google-containers/alpine-latest", + Digest: nil, + Purl: "pkg:oci/alpine-latest?repository_url=gcr.io/google-containers", + CPEs: nil, + } + + baselayoutPack = assembler.PackageNode{ + Name: "alpine-baselayout", + Digest: nil, + Purl: "pkg:alpine/alpine-baselayout@3.2.0-r22?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.2", + CPEs: []string{ + "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r22:*:*:*:*:*:*:*", + "cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.2.0-r22:*:*:*:*:*:*:*", + }, + } + + keysPack = assembler.PackageNode{ + Name: "alpine-keys", + Digest: nil, + Purl: "pkg:alpine/alpine-keys@2.4-r1?arch=x86_64&upstream=alpine-keys&distro=alpine-3.16.2", + CPEs: []string{ + "cpe:2.3:a:alpine-keys:alpine-keys:2.4-r1:*:*:*:*:*:*:*", + "cpe:2.3:a:alpine-keys:alpine_keys:2.4-r1:*:*:*:*:*:*:*", + "cpe:2.3:a:alpine:alpine-keys:2.4-r1:*:*:*:*:*:*:*", + "cpe:2.3:a:alpine:alpine_keys:2.4-r1:*:*:*:*:*:*:*", + }, + } + + baselayoutdataPack = assembler.PackageNode{ + Name: "alpine-baselayout-data", + Digest: nil, + Purl: "pkg:alpine/alpine-baselayout-data@3.2.0-r22?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.2", + CPEs: []string{ + "cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:3.2.0-r22:*:*:*:*:*:*:*", + "cpe:2.3:a:alpine-baselayout-data:alpine_baselayout_data:3.2.0-r22:*:*:*:*:*:*:*", + }, + } + + worldFile = assembler.ArtifactNode{ + Name: "/etc/apk/world", + Digest: "SHA256:713e3907167dce202d7c16034831af3d670191382a3e9026e0ac0a4023013201", + } + rootFile = assembler.ArtifactNode{ + Name: "/etc/crontabs/root", + Digest: "SHA256:575d810a9fae5f2f0671c9b2c0ce973e46c7207fbe5cb8d1b0d1836a6a0470e3", + } + triggersFile = assembler.ArtifactNode{ + Name: "/lib/apk/db/triggers", + Digest: "SHA256:5415cfe5f88c0af38df3b7141a3f9bc6b8178e9cf72d700658091b8f5539c7b4", + } + rsaPubFile = assembler.ArtifactNode{ + Name: "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + Digest: "SHA256:9a4cd858d9710963848e6d5f555325dc199d1c952b01cf6e64da2c15deedbd97", + } + + SpdxNodes = []assembler.GuacNode{topLevelPack, baselayoutPack, baselayoutdataPack, rsaPubFile, keysPack, worldFile, rootFile, triggersFile} + SpdxEdges = []assembler.GuacEdge{ + assembler.DependsOnEdge{ + PackageNode: topLevelPack, + PackageDependency: baselayoutPack, + }, + assembler.DependsOnEdge{ + PackageNode: topLevelPack, + PackageDependency: baselayoutdataPack, + }, + assembler.DependsOnEdge{ + PackageNode: topLevelPack, + PackageDependency: keysPack, + }, + assembler.DependsOnEdge{ + PackageNode: baselayoutPack, + PackageDependency: keysPack, + }, + assembler.DependsOnEdge{ + ArtifactNode: rootFile, + ArtifactDependency: rsaPubFile, + }, + assembler.ContainsEdge{ + PackageNode: baselayoutPack, + ContainedArtifact: rootFile, + }, + assembler.ContainsEdge{ + PackageNode: keysPack, + ContainedArtifact: rsaPubFile, + }, + } ) type mockSigstoreVerifier struct{} @@ -179,3 +272,100 @@ func (m *mockSigstoreVerifier) Verify(payloadBytes []byte) ([]verifier.Identity, func (m *mockSigstoreVerifier) Type() verifier.VerifierType { return "sigstore" } + +func GuacNodeSliceEqual(slice1, slice2 []assembler.GuacNode) bool { + if len(slice1) != len(slice2) { + return false + } + + result := true + + for _, node1 := range slice1 { + e := false + for _, node2 := range slice2 { + if node1.Type() == "Package" && node2.Type() == "Package" { + if node1.(assembler.PackageNode).Name == node2.(assembler.PackageNode).Name { + if reflect.DeepEqual(node1, node2) { + e = true + break + } + } + } else if node1.Type() == "Artifact" && node2.Type() == "Artifact" { + if node1.(assembler.ArtifactNode).Name == node2.(assembler.ArtifactNode).Name { + if reflect.DeepEqual(node1, node2) { + e = true + break + } + } + } else if node1.Type() == "Attestation" && node2.Type() == "Attestation" { + if node1.(assembler.AttestationNode).FilePath == node2.(assembler.AttestationNode).FilePath { + if reflect.DeepEqual(node1, node2) { + e = true + break + } + } + } else if node1.Type() == "Builder" && node2.Type() == "Builder" { + if node1.(assembler.BuilderNode).BuilderId == node2.(assembler.BuilderNode).BuilderId { + if reflect.DeepEqual(node1, node2) { + e = true + break + } + } + } else if node1.Type() == "Identity" && node2.Type() == "Identity" { + if node1.(assembler.IdentityNode).ID == node2.(assembler.IdentityNode).ID { + if reflect.DeepEqual(node1, node2) { + e = true + break + } + } + } + } + if !e { + result = false + } + } + return result +} + +func GuacEdgeSliceEqual(slice1, slice2 []assembler.GuacEdge) bool { + if len(slice1) != len(slice2) { + return false + } + + result := true + for _, edge1 := range slice1 { + e := false + for _, edge2 := range slice2 { + if edge1.Type() == "DependsOn" && edge2.Type() == "DependsOn" { + if reflect.DeepEqual(edge1, edge2) { + e = true + break + } + } else if edge1.Type() == "Contains" && edge2.Type() == "Contains" { + if reflect.DeepEqual(edge1, edge2) { + e = true + break + } + } else if edge1.Type() == "Attestation" && edge2.Type() == "Attestation" { + if reflect.DeepEqual(edge1, edge2) { + e = true + break + } + } else if edge1.Type() == "Identity" && edge2.Type() == "Identity" { + if reflect.DeepEqual(edge1, edge2) { + e = true + break + } + } else if edge1.Type() == "BuiltBy" && edge2.Type() == "BuiltBy" { + if reflect.DeepEqual(edge1, edge2) { + e = true + break + } + } + } + if !e { + result = false + } + } + return result +} diff --git a/internal/testing/processor/testdata.go b/internal/testing/processor/testdata.go index f722313769..b27fcda9ea 100644 --- a/internal/testing/processor/testdata.go +++ b/internal/testing/processor/testdata.go @@ -25,6 +25,9 @@ var ( //go:embed testdata/alpine-spdx.json SpdxExampleBig []byte + //go:embed testdata/alpine-small-spdx.json + SpdxExampleAlpine []byte + // Invalid types for field spdxVersion //go:embed testdata/invalid-spdx.json SpdxInvalidExample []byte diff --git a/internal/testing/processor/testdata/alpine-small-spdx.json b/internal/testing/processor/testdata/alpine-small-spdx.json new file mode 100644 index 0000000000..0b1dbe8f20 --- /dev/null +++ b/internal/testing/processor/testdata/alpine-small-spdx.json @@ -0,0 +1,633 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "name": "gcr.io/google-containers/alpine-latest", + "spdxVersion": "SPDX-2.2", + "creationInfo": { + "created": "2022-09-24T17:27:55.556104Z", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-0.57.0" + ], + "licenseListVersion": "3.18" + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "https://anchore.com/syft/image/alpine-latest-e78eca08-d9f4-49c7-97e0-6d4b9bfa99c2", + "packages": [ + { + "SPDXID": "SPDXRef-35085779bdf473bb", + "name": "alpine-baselayout", + "licenseConcluded": "GPL-2.0-only", + "description": "Alpine base dir structure and init scripts", + "downloadLocation": "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r22:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.2.0-r22:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:alpine/alpine-baselayout@3.2.0-r22?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.2", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "hasFiles": [ + "SPDXRef-1ee0f450becc786f", + "SPDXRef-336bc8ce40e7fc42", + "SPDXRef-3cf575889d9cc66c", + "SPDXRef-5be401ad758d7c8", + "SPDXRef-757351ee498badd7", + "SPDXRef-786c4e711c1a558b", + "SPDXRef-88b0f6fae4de13a0", + "SPDXRef-a6c4c4e977ddf6d8", + "SPDXRef-cb0990ff1c4365e4", + "SPDXRef-da399cec16efc781", + "SPDXRef-de2a9cb8a967fb5b" + ], + "licenseDeclared": "GPL-2.0-only", + "originator": "Person: Natanael Copa <ncopa@alpinelinux.org>", + "sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed", + "versionInfo": "3.2.0-r22" + }, + { + "SPDXID": "SPDXRef-33b5ab4a81e975bd", + "name": "alpine-baselayout-data", + "licenseConcluded": "GPL-2.0-only", + "description": "Alpine base dir structure and init scripts", + "downloadLocation": "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:3.2.0-r22:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-baselayout-data:alpine_baselayout_data:3.2.0-r22:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:alpine/alpine-baselayout-data@3.2.0-r22?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.2", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "hasFiles": [ + "SPDXRef-14c57fa7ac8df92", + "SPDXRef-2ac427870f248704", + "SPDXRef-2be29626dd7a31e2", + "SPDXRef-2e3b308d2192da55", + "SPDXRef-4cb78c1b83f3f6fa", + "SPDXRef-58256f3c5c4e6aa2", + "SPDXRef-5b4c64f05d9b355a", + "SPDXRef-5c71002e828599e6", + "SPDXRef-5ec7e40e4299d952", + "SPDXRef-75b6ed72d694a357", + "SPDXRef-795c342188acc719", + "SPDXRef-9622c4a77c0af92d", + "SPDXRef-bd8e6d084d722e0a" + ], + "licenseDeclared": "GPL-2.0-only", + "originator": "Person: Natanael Copa <ncopa@alpinelinux.org>", + "sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed", + "versionInfo": "3.2.0-r22" + }, + { + "SPDXID": "SPDXRef-3f53edc3b14056c3", + "name": "alpine-keys", + "licenseConcluded": "MIT", + "description": "Public keys for Alpine Linux packages", + "downloadLocation": "https://alpinelinux.org", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-keys:alpine-keys:2.4-r1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine-keys:alpine_keys:2.4-r1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine:alpine-keys:2.4-r1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:alpine:alpine_keys:2.4-r1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:alpine/alpine-keys@2.4-r1?arch=x86_64&upstream=alpine-keys&distro=alpine-3.16.2", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "hasFiles": [ + "SPDXRef-14473a45c2af16d7", + "SPDXRef-156d627c97a2de34", + "SPDXRef-1ee1cd40588ab89c", + "SPDXRef-221af60be84b09c0", + "SPDXRef-274572174bc1cc7a", + "SPDXRef-300f983a142f9504", + "SPDXRef-44193297ee82bac1", + "SPDXRef-45232e260abd77f7", + "SPDXRef-492cf038d1d9fd9b", + "SPDXRef-4cbd1b18ddd59c42", + "SPDXRef-4d1c352ad50e20b2", + "SPDXRef-6d7742dc4838b698", + "SPDXRef-716461c423874936", + "SPDXRef-879bdb5c61068a44", + "SPDXRef-9b559b61986fccb0", + "SPDXRef-af1d9aa588b56c47", + "SPDXRef-c549a0b76f823487", + "SPDXRef-eb93193a7276c76a", + "SPDXRef-f542a07f45615070", + "SPDXRef-f91e100c74bf27e", + "SPDXRef-f96f56f789a464ad", + "SPDXRef-fb57f5df1fd169db" + ], + "licenseDeclared": "MIT", + "originator": "Person: Natanael Copa <ncopa@alpinelinux.org>", + "sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed", + "versionInfo": "2.4-r1" + } + ], + "files": [ + { + "SPDXID": "SPDXRef-a3cc05285a46b7f7", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "fileName": "/bin", + "fileTypes": [ + "OTHER" + ] + }, + { + "SPDXID": "SPDXRef-9936d4f0772f184e", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "713e3907167dce202d7c16034831af3d670191382a3e9026e0ac0a4023013201" + } + ], + "fileName": "/etc/apk/world", + "fileTypes": [ + "TEXT" + ] + }, + { + "SPDXID": "SPDXRef-5be401ad758d7c8", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "575d810a9fae5f2f0671c9b2c0ce973e46c7207fbe5cb8d1b0d1836a6a0470e3" + } + ], + "fileName": "/etc/crontabs/root", + "fileTypes": [ + "TEXT" + ] + }, + { + "SPDXID": "SPDXRef-6cf3a5a9353a152d", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "5415cfe5f88c0af38df3b7141a3f9bc6b8178e9cf72d700658091b8f5539c7b4" + } + ], + "fileName": "/lib/apk/db/triggers", + "fileTypes": [ + "TEXT" + ] + }, + { + "SPDXID": "SPDXRef-9b559b61986fccb0", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "9a4cd858d9710963848e6d5f555325dc199d1c952b01cf6e64da2c15deedbd97" + } + ], + "fileName": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "fileTypes": [ + "TEXT" + ] + }, + { + "SPDXID": "SPDXRef-659b325adddd783e", + "comment": "layerID: sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + "licenseConcluded": "NOASSERTION", + "fileName": "/var/tmp", + "fileTypes": [ + "OTHER" + ] + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-2bc2db5bac1d0fe4", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1ba0b361ecdca2c4" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-2ac427870f248704" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": "SPDXRef-3f53edc3b14056c3" + }, + { + "spdxElementId": "SPDXRef-2bc2db5bac1d0fe4", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-7dc15fca12e2f017" + }, + { + "spdxElementId": "SPDXRef-5be401ad758d7c8", + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": "SPDXRef-9b559b61986fccb0" + }, + { + "spdxElementId": "SPDXRef-2bc2db5bac1d0fe4", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-8197f64c214a5a16" + }, + { + "spdxElementId": "SPDXRef-2bc2db5bac1d0fe4", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-9ce55bcb43ee284f" + }, + { + "spdxElementId": "SPDXRef-2bc2db5bac1d0fe4", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-f475459004544a56" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-14c57fa7ac8df92" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-2ac427870f248704" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-2be29626dd7a31e2" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-2e3b308d2192da55" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-4cb78c1b83f3f6fa" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-58256f3c5c4e6aa2" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-5b4c64f05d9b355a" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-5c71002e828599e6" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-5ec7e40e4299d952" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-75b6ed72d694a357" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-795c342188acc719" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-9622c4a77c0af92d" + }, + { + "spdxElementId": "SPDXRef-33b5ab4a81e975bd", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-bd8e6d084d722e0a" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1ee0f450becc786f" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-336bc8ce40e7fc42" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-3cf575889d9cc66c" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-5be401ad758d7c8" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-757351ee498badd7" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-786c4e711c1a558b" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-88b0f6fae4de13a0" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-a6c4c4e977ddf6d8" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-cb0990ff1c4365e4" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-da399cec16efc781" + }, + { + "spdxElementId": "SPDXRef-35085779bdf473bb", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-de2a9cb8a967fb5b" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-14473a45c2af16d7" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-156d627c97a2de34" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1ee1cd40588ab89c" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-221af60be84b09c0" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-274572174bc1cc7a" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-300f983a142f9504" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-44193297ee82bac1" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-45232e260abd77f7" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-492cf038d1d9fd9b" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-4cbd1b18ddd59c42" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-4d1c352ad50e20b2" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-6d7742dc4838b698" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-716461c423874936" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-879bdb5c61068a44" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-9b559b61986fccb0" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-af1d9aa588b56c47" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-c549a0b76f823487" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-eb93193a7276c76a" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-f542a07f45615070" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-f91e100c74bf27e" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-f96f56f789a464ad" + }, + { + "spdxElementId": "SPDXRef-3f53edc3b14056c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-fb57f5df1fd169db" + }, + { + "spdxElementId": "SPDXRef-3ff09d7a5e0dc2ed", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-754289e667437895" + }, + { + "spdxElementId": "SPDXRef-7514a98b23f9928c", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-19e9882925c797f5" + }, + { + "spdxElementId": "SPDXRef-7aec2be3ffd82c3c", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-a74d39439f2d84b8" + }, + { + "spdxElementId": "SPDXRef-94e7e84b87c1f8c3", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-6d1b682f6d48f488" + }, + { + "spdxElementId": "SPDXRef-9bb9cb82a4ce72b1", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-4fb0c07667dac902" + }, + { + "spdxElementId": "SPDXRef-9bb9cb82a4ce72b1", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-d8bdade972f61759" + }, + { + "spdxElementId": "SPDXRef-b703a8e4e90dd6dc", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1b08cd6c03818e29" + }, + { + "spdxElementId": "SPDXRef-b703a8e4e90dd6dc", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-2cd2aeb015390775" + }, + { + "spdxElementId": "SPDXRef-b703a8e4e90dd6dc", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-4a324ad304be8e9a" + }, + { + "spdxElementId": "SPDXRef-b703a8e4e90dd6dc", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-98233f67f18b2755" + }, + { + "spdxElementId": "SPDXRef-b703a8e4e90dd6dc", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-c1d35477db673e2d" + }, + { + "spdxElementId": "SPDXRef-bebc881007d932d", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-535cfe0185d18797" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1087f474228124bb" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-1b1e12f00cbb2df9" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-28854548e0d878c2" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-61a86d4a797602e5" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-c35e7c8840928dba" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-c3c38e46778cd717" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-dd0104ad41122fa2" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-e5e1738bbb13275f" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-eb47016cd05f7d35" + }, + { + "spdxElementId": "SPDXRef-cce075f4f19baaee", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-fa7856d6d0b238f1" + }, + { + "spdxElementId": "SPDXRef-ec1d619a28263eb0", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-e2c90ae8ae67431f" + } + ] +} diff --git a/pkg/ingestor/parser/common/graph_builder.go b/pkg/ingestor/parser/common/graph_builder.go index 01aebae7bf..dda280a8eb 100644 --- a/pkg/ingestor/parser/common/graph_builder.go +++ b/pkg/ingestor/parser/common/graph_builder.go @@ -16,6 +16,8 @@ package common import ( + "context" + "github.com/guacsec/guac/pkg/assembler" ) @@ -34,10 +36,10 @@ func NewGenericGraphBuilder(docParser DocumentParser, foundIdentities []assemble } // CreateAssemblerInput creates the GuacNodes and GuacEdges that are needed by the assembler -func (b *GraphBuilder) CreateAssemblerInput(foundIdentities []assembler.IdentityNode) assembler.AssemblerInput { +func (b *GraphBuilder) CreateAssemblerInput(ctx context.Context, foundIdentities []assembler.IdentityNode) assembler.AssemblerInput { assemblerinput := assembler.AssemblerInput{ Nodes: b.docParser.CreateNodes(), - Edges: b.docParser.CreateEdges(foundIdentities), + Edges: b.docParser.CreateEdges(ctx, foundIdentities), } return assemblerinput } diff --git a/pkg/ingestor/parser/common/types.go b/pkg/ingestor/parser/common/types.go index 6bfb8534a8..00b9034f5b 100644 --- a/pkg/ingestor/parser/common/types.go +++ b/pkg/ingestor/parser/common/types.go @@ -16,6 +16,8 @@ package common import ( + "context" + "github.com/guacsec/guac/pkg/assembler" "github.com/guacsec/guac/pkg/handler/processor" ) @@ -28,5 +30,5 @@ type DocumentParser interface { // CreateNodes creates the GuacNode for the graph inputs CreateNodes() []assembler.GuacNode // CreateEdges creates the GuacEdges that form the relationship for the graph inputs - CreateEdges(foundIdentities []assembler.IdentityNode) []assembler.GuacEdge + CreateEdges(ctx context.Context, foundIdentities []assembler.IdentityNode) []assembler.GuacEdge } diff --git a/pkg/ingestor/parser/dsse/parser_dsse.go b/pkg/ingestor/parser/dsse/parser_dsse.go index 4eec63e5ef..9a0deccedc 100644 --- a/pkg/ingestor/parser/dsse/parser_dsse.go +++ b/pkg/ingestor/parser/dsse/parser_dsse.go @@ -16,6 +16,7 @@ package dsse import ( + "context" "encoding/base64" "fmt" @@ -76,6 +77,6 @@ func (d *dsseParser) CreateNodes() []assembler.GuacNode { } // CreateEdges creates the GuacEdges that form the relationship for the graph inputs -func (d *dsseParser) CreateEdges(foundIdentities []assembler.IdentityNode) []assembler.GuacEdge { +func (d *dsseParser) CreateEdges(ctx context.Context, foundIdentities []assembler.IdentityNode) []assembler.GuacEdge { return []assembler.GuacEdge{} } diff --git a/pkg/ingestor/parser/dsse/parser_dsse_test.go b/pkg/ingestor/parser/dsse/parser_dsse_test.go index a056625ad1..def262482e 100644 --- a/pkg/ingestor/parser/dsse/parser_dsse_test.go +++ b/pkg/ingestor/parser/dsse/parser_dsse_test.go @@ -16,6 +16,7 @@ package dsse import ( + "context" "reflect" "testing" @@ -23,21 +24,29 @@ import ( "github.com/guacsec/guac/pkg/assembler" "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor/verifier" + "github.com/guacsec/guac/pkg/logging" ) func Test_DsseParser(t *testing.T) { + ctx := logging.WithLogger(context.Background()) err := verifier.RegisterVerifier(testdata.NewMockSigstoreVerifier(), "sigstore") if err != nil { t.Errorf("verifier.RegisterVerifier() failed with error: %v", err) } tests := []struct { - name string - doc *processor.Document - wantErr bool + name string + doc *processor.Document + wantNodes []assembler.GuacNode + wantEdges []assembler.GuacEdge + wantIdentity assembler.IdentityNode + wantErr bool }{{ - name: "testing", - doc: &testdata.Ite6DSSEDoc, - wantErr: false, + name: "testing", + doc: &testdata.Ite6DSSEDoc, + wantNodes: testdata.DsseNodes, + wantEdges: testdata.DsseEdges, + wantIdentity: testdata.Ident, + wantErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -45,14 +54,14 @@ func Test_DsseParser(t *testing.T) { if err := d.Parse(tt.doc); (err != nil) != tt.wantErr { t.Errorf("slsa.Parse() error = %v, wantErr %v", err, tt.wantErr) } - if nodes := d.CreateNodes(); !reflect.DeepEqual(nodes, testdata.DsseNodes) { - t.Errorf("slsa.CreateNodes() = %v, want %v", nodes, testdata.DsseNodes) + if nodes := d.CreateNodes(); !reflect.DeepEqual(nodes, tt.wantNodes) { + t.Errorf("slsa.CreateNodes() = %v, want %v", nodes, tt.wantNodes) } - if edges := d.CreateEdges([]assembler.IdentityNode{testdata.Ident}); !reflect.DeepEqual(edges, testdata.DsseEdges) { - t.Errorf("slsa.CreateEdges() = %v, want %v", edges, testdata.DsseEdges) + if edges := d.CreateEdges(ctx, []assembler.IdentityNode{tt.wantIdentity}); !reflect.DeepEqual(edges, tt.wantEdges) { + t.Errorf("slsa.CreateEdges() = %v, want %v", edges, tt.wantEdges) } - if identity := d.GetIdentities(); !reflect.DeepEqual(identity, []assembler.IdentityNode{testdata.Ident}) { - t.Errorf("slsa.GetDocType() = %v, want %v", identity, []assembler.IdentityNode{testdata.Ident}) + if identity := d.GetIdentities(); !reflect.DeepEqual(identity, []assembler.IdentityNode{tt.wantIdentity}) { + t.Errorf("slsa.GetDocType() = %v, want %v", identity, []assembler.IdentityNode{tt.wantIdentity}) } }) } diff --git a/pkg/ingestor/parser/parser.go b/pkg/ingestor/parser/parser.go index a1f54996a1..c4e6cb555c 100644 --- a/pkg/ingestor/parser/parser.go +++ b/pkg/ingestor/parser/parser.go @@ -16,6 +16,7 @@ package parser import ( + "context" "fmt" "github.com/guacsec/guac/pkg/assembler" @@ -23,6 +24,7 @@ import ( "github.com/guacsec/guac/pkg/ingestor/parser/common" "github.com/guacsec/guac/pkg/ingestor/parser/dsse" "github.com/guacsec/guac/pkg/ingestor/parser/slsa" + "github.com/guacsec/guac/pkg/ingestor/parser/spdx" ) type docTreeBuilder struct { @@ -38,7 +40,7 @@ func newDocTreeBuilder() *docTreeBuilder { } // ParseDocumentTree takes the DocumentTree and create graph inputs (nodes and edges) per document node -func ParseDocumentTree(docTree processor.DocumentTree) ([]assembler.AssemblerInput, error) { +func ParseDocumentTree(ctx context.Context, docTree processor.DocumentTree) ([]assembler.AssemblerInput, error) { assemblerinputs := []assembler.AssemblerInput{} docTreeBuilder := newDocTreeBuilder() err := docTreeBuilder.parse(docTree) @@ -46,7 +48,7 @@ func ParseDocumentTree(docTree processor.DocumentTree) ([]assembler.AssemblerInp return nil, err } for _, builder := range docTreeBuilder.graphBuilders { - assemblerinput := builder.CreateAssemblerInput(docTreeBuilder.identities) + assemblerinput := builder.CreateAssemblerInput(ctx, docTreeBuilder.identities) assemblerinputs = append(assemblerinputs, assemblerinput) } @@ -93,6 +95,14 @@ func parseHelper(doc *processor.Document) (*common.GraphBuilder, error) { } slsaGraphBuilder := common.NewGenericGraphBuilder(slsaParser, nil) return slsaGraphBuilder, nil + case processor.DocumentSPDX: + spdxParser := spdx.NewSpdxParser() + err := spdxParser.Parse(doc) + if err != nil { + return nil, err + } + spdxGraphBuilder := common.NewGenericGraphBuilder(spdxParser, nil) + return spdxGraphBuilder, nil } return nil, fmt.Errorf("no parser found for document type: %v", doc.Type) } diff --git a/pkg/ingestor/parser/parser_test.go b/pkg/ingestor/parser/parser_test.go index af92859340..63da2e8c94 100644 --- a/pkg/ingestor/parser/parser_test.go +++ b/pkg/ingestor/parser/parser_test.go @@ -16,17 +16,19 @@ package parser import ( - "reflect" + "context" "testing" "github.com/guacsec/guac/internal/testing/ingestor/testdata" + processor_data "github.com/guacsec/guac/internal/testing/processor" "github.com/guacsec/guac/pkg/assembler" "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor/verifier" + "github.com/guacsec/guac/pkg/logging" ) var ( - docTree = processor.DocumentNode{ + dsseDocTree = processor.DocumentNode{ Document: &testdata.Ite6DSSEDoc, Children: []*processor.DocumentNode{ { @@ -36,6 +38,16 @@ var ( }, } + spdxDocTree = processor.DocumentNode{ + Document: &processor.Document{ + Blob: processor_data.SpdxExampleAlpine, + Format: processor.FormatJSON, + Type: processor.DocumentSPDX, + SourceInformation: processor.SourceInformation{}, + }, + Children: []*processor.DocumentNode{}, + } + graphInput = []assembler.AssemblerInput{{ Nodes: testdata.DsseNodes, Edges: testdata.DsseEdges, @@ -43,9 +55,15 @@ var ( Nodes: testdata.SlsaNodes, Edges: testdata.SlsaEdges, }} + + spdxGraphInput = []assembler.AssemblerInput{{ + Nodes: testdata.SpdxNodes, + Edges: testdata.SpdxEdges, + }} ) func TestParseDocumentTree(t *testing.T) { + ctx := logging.WithLogger(context.Background()) err := verifier.RegisterVerifier(testdata.NewMockSigstoreVerifier(), "sigstore") if err != nil { t.Errorf("verifier.RegisterVerifier() failed with error: %v", err) @@ -57,20 +75,39 @@ func TestParseDocumentTree(t *testing.T) { wantErr bool }{{ name: "testing", - tree: processor.DocumentTree(&docTree), + tree: processor.DocumentTree(&dsseDocTree), want: graphInput, wantErr: false, + }, { + name: "valid big SPDX document", + tree: processor.DocumentTree(&spdxDocTree), + want: spdxGraphInput, + wantErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ParseDocumentTree(tt.tree) + got, err := ParseDocumentTree(ctx, tt.tree) if (err != nil) != tt.wantErr { t.Errorf("ParseDocumentTree() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseDocumentTree() = %v, want %v", got, tt.want) + if err == nil { + if len(got) != len(tt.want) { + t.Errorf("ParseDocumentTree() = %v, want %v", got, tt.want) + } + for i := range got { + compare(t, got[i].Edges, tt.want[i].Edges, got[i].Nodes, tt.want[i].Nodes) + } } }) } } + +func compare(t *testing.T, gotEdges, wantEdges []assembler.GuacEdge, gotNodes, wantNodes []assembler.GuacNode) { + if !testdata.GuacEdgeSliceEqual(gotEdges, wantEdges) { + t.Errorf("ParseDocumentTree() = %v, want %v", gotEdges, wantEdges) + } + if !testdata.GuacNodeSliceEqual(gotNodes, wantNodes) { + t.Errorf("ParseDocumentTree() = %v, want %v", gotNodes, wantNodes) + } +} diff --git a/pkg/ingestor/parser/slsa/parser_slsa.go b/pkg/ingestor/parser/slsa/parser_slsa.go index 85c4600245..05f8d39dcc 100644 --- a/pkg/ingestor/parser/slsa/parser_slsa.go +++ b/pkg/ingestor/parser/slsa/parser_slsa.go @@ -16,6 +16,7 @@ package slsa import ( + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -119,7 +120,7 @@ func (s *slsaParser) CreateNodes() []assembler.GuacNode { } // CreateEdges creates the GuacEdges that form the relationship for the graph inputs -func (s *slsaParser) CreateEdges(foundIdentities []assembler.IdentityNode) []assembler.GuacEdge { +func (s *slsaParser) CreateEdges(ctx context.Context, foundIdentities []assembler.IdentityNode) []assembler.GuacEdge { edges := []assembler.GuacEdge{} for _, i := range foundIdentities { for _, a := range s.attestations { diff --git a/pkg/ingestor/parser/slsa/parser_slsa_test.go b/pkg/ingestor/parser/slsa/parser_slsa_test.go index 778eaf0cf7..81a65d9acb 100644 --- a/pkg/ingestor/parser/slsa/parser_slsa_test.go +++ b/pkg/ingestor/parser/slsa/parser_slsa_test.go @@ -16,23 +16,30 @@ package slsa import ( + "context" "reflect" "testing" "github.com/guacsec/guac/internal/testing/ingestor/testdata" "github.com/guacsec/guac/pkg/assembler" "github.com/guacsec/guac/pkg/handler/processor" + "github.com/guacsec/guac/pkg/logging" ) func Test_slsaParser(t *testing.T) { + ctx := logging.WithLogger(context.Background()) tests := []struct { - name string - doc *processor.Document - wantErr bool + name string + doc *processor.Document + wantNodes []assembler.GuacNode + wantEdges []assembler.GuacEdge + wantErr bool }{{ - name: "testing", - doc: &testdata.Ite6SLSADoc, - wantErr: false, + name: "testing", + doc: &testdata.Ite6SLSADoc, + wantNodes: testdata.SlsaNodes, + wantEdges: testdata.SlsaEdges, + wantErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -40,11 +47,11 @@ func Test_slsaParser(t *testing.T) { if err := s.Parse(tt.doc); (err != nil) != tt.wantErr { t.Errorf("slsa.Parse() error = %v, wantErr %v", err, tt.wantErr) } - if nodes := s.CreateNodes(); !reflect.DeepEqual(nodes, testdata.SlsaNodes) { - t.Errorf("slsa.CreateNodes() = %v, want %v", nodes, testdata.SlsaNodes) + if nodes := s.CreateNodes(); !reflect.DeepEqual(nodes, tt.wantNodes) { + t.Errorf("slsa.CreateNodes() = %v, want %v", nodes, tt.wantNodes) } - if edges := s.CreateEdges([]assembler.IdentityNode{testdata.Ident}); !reflect.DeepEqual(edges, testdata.SlsaEdges) { - t.Errorf("slsa.CreateEdges() = %v, want %v", edges, testdata.SlsaEdges) + if edges := s.CreateEdges(ctx, []assembler.IdentityNode{testdata.Ident}); !reflect.DeepEqual(edges, tt.wantEdges) { + t.Errorf("slsa.CreateEdges() = %v, want %v", edges, tt.wantEdges) } }) } diff --git a/pkg/ingestor/parser/spdx/parse_spdx.go b/pkg/ingestor/parser/spdx/parse_spdx.go new file mode 100644 index 0000000000..39249d8a8c --- /dev/null +++ b/pkg/ingestor/parser/spdx/parse_spdx.go @@ -0,0 +1,254 @@ +// +// Copyright 2022 The GUAC Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spdx + +import ( + "bytes" + "context" + "errors" + "fmt" + "strings" + + "github.com/guacsec/guac/pkg/assembler" + "github.com/guacsec/guac/pkg/handler/processor" + "github.com/guacsec/guac/pkg/logging" + spdx_json "github.com/spdx/tools-golang/json" + spdx_common "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_2" +) + +type spdxParser struct { + packages map[string][]assembler.PackageNode + files map[string][]assembler.ArtifactNode + spdxDoc *v2_2.Document +} + +func NewSpdxParser() *spdxParser { + return &spdxParser{ + packages: map[string][]assembler.PackageNode{}, + files: map[string][]assembler.ArtifactNode{}, + } +} + +func (s *spdxParser) Parse(doc *processor.Document) error { + spdxDoc, err := parseSpdxBlob(doc.Blob) + if err != nil { + return fmt.Errorf("failed to parse SPDX document: %w", err) + } + s.spdxDoc = spdxDoc + s.getPackages() + s.getFiles() + return nil +} + +// creating top level package manually until https://github.com/anchore/syft/issues/1241 is resolved +func (s *spdxParser) getTopLevelPackage() { + // oci purl: pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=ghcr.io/debian&tag=bullseye + splitImage := strings.Split(s.spdxDoc.DocumentName, "/") + if len(splitImage) == 3 { + topPackage := assembler.PackageNode{} + topPackage.Purl = "pkg:oci/" + splitImage[2] + "?repository_url=" + splitImage[0] + "/" + splitImage[1] + topPackage.Name = s.spdxDoc.DocumentName + s.packages[string(s.spdxDoc.SPDXIdentifier)] = append(s.packages[string(s.spdxDoc.SPDXIdentifier)], topPackage) + } +} + +func (s *spdxParser) getPackages() { + s.getTopLevelPackage() + for _, pac := range s.spdxDoc.Packages { + currentPackage := assembler.PackageNode{} + currentPackage.Name = pac.PackageName + if len(pac.PackageExternalReferences) > 0 { + for _, ext := range pac.PackageExternalReferences { + if strings.HasPrefix(ext.RefType, "cpe") { + currentPackage.CPEs = append(currentPackage.CPEs, ext.Locator) + } else if ext.RefType == spdx_common.TypePackageManagerPURL { + currentPackage.Purl = ext.Locator + } + } + } + if len(pac.PackageChecksums) > 0 { + for _, checksum := range pac.PackageChecksums { + currentPackage.Digest = append(currentPackage.Digest, string(checksum.Algorithm)+":"+checksum.Value) + } + } + s.packages[string(pac.PackageSPDXIdentifier)] = append(s.packages[string(pac.PackageSPDXIdentifier)], currentPackage) + } +} + +func (s *spdxParser) getFiles() { + for _, file := range s.spdxDoc.Files { + currentFile := assembler.ArtifactNode{} + for _, checksum := range file.Checksums { + currentFile.Name = file.FileName + currentFile.Digest = string(checksum.Algorithm) + ":" + checksum.Value + s.files[string(file.FileSPDXIdentifier)] = append(s.files[string(file.FileSPDXIdentifier)], currentFile) + } + } +} + +func parseSpdxBlob(p []byte) (*v2_2.Document, error) { + reader := bytes.NewReader(p) + spdx, err := spdx_json.Load2_2(reader) + if err != nil { + return nil, err + } + return spdx, nil +} + +func (s *spdxParser) CreateNodes() []assembler.GuacNode { + nodes := []assembler.GuacNode{} + for _, packNodes := range s.packages { + for _, packNode := range packNodes { + nodes = append(nodes, packNode) + } + } + for _, fileNodes := range s.files { + for _, fileNode := range fileNodes { + nodes = append(nodes, fileNode) + } + } + return nodes +} + +func (s *spdxParser) getPackageElement(elementID string) []assembler.PackageNode { + if packNode, ok := s.packages[string(elementID)]; ok { + return packNode + } + return nil +} + +func (s *spdxParser) getFileElement(elementID string) []assembler.ArtifactNode { + if fileNode, ok := s.files[string(elementID)]; ok { + return fileNode + } + return nil +} + +func (s *spdxParser) CreateEdges(ctx context.Context, foundIdentities []assembler.IdentityNode) []assembler.GuacEdge { + logger := logging.FromContext(ctx) + edges := []assembler.GuacEdge{} + toplevel := s.getPackageElement("SPDXRef-DOCUMENT") + // adding top level package edge manually for all depends on package + if toplevel != nil { + edges = append(edges, createTopLevelEdges(toplevel[0], s.packages)...) + } + for _, rel := range s.spdxDoc.Relationships { + foundPackNodes := s.getPackageElement("SPDXRef-" + string(rel.RefA.ElementRefID)) + foundFileNodes := s.getFileElement("SPDXRef-" + string(rel.RefA.ElementRefID)) + relatedPackNodes := s.getPackageElement("SPDXRef-" + string(rel.RefB.ElementRefID)) + relatedFileNodes := s.getFileElement("SPDXRef-" + string(rel.RefB.ElementRefID)) + for _, packNode := range foundPackNodes { + createdEdge, err := getEdge(packNode, rel.Relationship, relatedPackNodes, relatedFileNodes) + if err != nil { + logger.Errorf("error generating spdx edge %v", err) + continue + } + if createdEdge != nil { + edges = append(edges, createdEdge) + } + } + for _, fileNode := range foundFileNodes { + createdEdge, err := getEdge(fileNode, rel.Relationship, relatedPackNodes, relatedFileNodes) + if err != nil { + logger.Errorf("error generating spdx edge %v", err) + continue + } + if createdEdge != nil { + edges = append(edges, createdEdge) + } + } + } + return edges +} + +func createTopLevelEdges(toplevel assembler.PackageNode, packages map[string][]assembler.PackageNode) []assembler.GuacEdge { + edges := []assembler.GuacEdge{} + for _, packNodes := range packages { + for _, packNode := range packNodes { + if packNode.Purl != toplevel.Purl { + e := assembler.DependsOnEdge{ + PackageNode: toplevel, + PackageDependency: packNode, + } + edges = append(edges, e) + } + } + } + return edges +} + +func getEdge(foundNode assembler.GuacNode, relationship string, relatedPackNodes []assembler.PackageNode, relatedFileNodes []assembler.ArtifactNode) (assembler.GuacEdge, error) { + if len(relatedFileNodes) > 0 { + for _, rfileNode := range relatedFileNodes { + return getEdgeByType(relationship, foundNode, rfileNode) + } + } else if len(relatedPackNodes) > 0 { + for _, rpackNode := range relatedPackNodes { + return getEdgeByType(relationship, foundNode, rpackNode) + } + } + return nil, nil +} + +func getEdgeByType(relationship string, foundNode assembler.GuacNode, relatedNode assembler.GuacNode) (assembler.GuacEdge, error) { + switch relationship { + case spdx_common.TypeRelationshipContains: + return getContainsEdge(foundNode, relatedNode) + case spdx_common.TypeRelationshipDependsOn: + return getDependsOnEdge(foundNode, relatedNode), nil + } + return nil, nil +} + +func getContainsEdge(foundNode assembler.GuacNode, relatedNode assembler.GuacNode) (assembler.GuacEdge, error) { + e := assembler.ContainsEdge{} + if foundNode.Type() == "Package" { + e.PackageNode = foundNode.(assembler.PackageNode) + } else { + return nil, errors.New("node type mismatch during contains edge creation") + } + if relatedNode.Type() == "Artifact" { + e.ContainedArtifact = relatedNode.(assembler.ArtifactNode) + } else { + return nil, errors.New("node type mismatch during contains edge creation") + } + return e, nil +} + +func getDependsOnEdge(foundNode assembler.GuacNode, relatedNode assembler.GuacNode) assembler.GuacEdge { + e := assembler.DependsOnEdge{} + if foundNode.Type() == "Package" { + e.PackageNode = foundNode.(assembler.PackageNode) + } else { + e.ArtifactNode = foundNode.(assembler.ArtifactNode) + } + + if relatedNode.Type() == "Package" { + e.PackageDependency = relatedNode.(assembler.PackageNode) + } else { + e.ArtifactDependency = relatedNode.(assembler.ArtifactNode) + } + return e +} + +func (s *spdxParser) GetIdentities() []assembler.IdentityNode { + return nil +} + +func (s *spdxParser) GetDocType() processor.DocumentType { + return processor.DocumentSPDX +} diff --git a/pkg/ingestor/parser/spdx/parse_spdx_test.go b/pkg/ingestor/parser/spdx/parse_spdx_test.go new file mode 100644 index 0000000000..de389756a0 --- /dev/null +++ b/pkg/ingestor/parser/spdx/parse_spdx_test.go @@ -0,0 +1,70 @@ +// +// Copyright 2022 The GUAC Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spdx + +import ( + "context" + "reflect" + "testing" + + "github.com/guacsec/guac/internal/testing/ingestor/testdata" + processor_data "github.com/guacsec/guac/internal/testing/processor" + "github.com/guacsec/guac/pkg/assembler" + "github.com/guacsec/guac/pkg/handler/processor" + "github.com/guacsec/guac/pkg/logging" +) + +func Test_spdxParser(t *testing.T) { + ctx := logging.WithLogger(context.Background()) + tests := []struct { + name string + doc *processor.Document + wantNodes []assembler.GuacNode + wantEdges []assembler.GuacEdge + wantErr bool + }{{ + name: "valid big SPDX document", + doc: &processor.Document{ + Blob: processor_data.SpdxExampleAlpine, + Format: processor.FormatJSON, + Type: processor.DocumentSPDX, + SourceInformation: processor.SourceInformation{}, + }, + wantNodes: testdata.SpdxNodes, + wantEdges: testdata.SpdxEdges, + wantErr: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewSpdxParser() + err := s.Parse(tt.doc) + if (err != nil) != tt.wantErr { + t.Errorf("spdxParser.Parse() error = %v, wantErr %v", err, tt.wantErr) + } + if err == nil { + if nodes := s.CreateNodes(); !testdata.GuacNodeSliceEqual(nodes, tt.wantNodes) { + t.Errorf("spdxParser.CreateNodes() = %v, want %v", nodes, tt.wantNodes) + } + if edges := s.CreateEdges(ctx, nil); !testdata.GuacEdgeSliceEqual(edges, tt.wantEdges) { + t.Errorf("spdxParser.CreateEdges() = %v, want %v", edges, tt.wantEdges) + } + if docType := s.GetDocType(); !reflect.DeepEqual(docType, processor.DocumentSPDX) { + t.Errorf("spdxParser.GetDocType() = %v, want %v", docType, processor.DocumentSPDX) + } + } + }) + } +}