mirror of
https://github.com/safedep/vet.git
synced 2025-12-10 00:22:08 -06:00
feat: Refactor CycloneDX parser into CycloneDX Graph Parser
refactor: CDX graph parser to improve readability fix: Set dependency graph present only when BOM contains at least 1 dependency relation chore: Add a root note while graph rendering (reporter) chore: Remove old cyclonedx files test: Add maven cyclonedx sbom test case
This commit is contained in:
parent
774323c28f
commit
b662145492
@ -65,6 +65,10 @@ func (pm *PackageManifest) AddPackage(pkg *Package) {
|
||||
pm.m.Lock()
|
||||
defer pm.m.Unlock()
|
||||
|
||||
if pkg.Manifest == nil {
|
||||
pkg.Manifest = pm
|
||||
}
|
||||
|
||||
pm.Packages = append(pm.Packages, pkg)
|
||||
pm.DependencyGraph.AddNode(pkg)
|
||||
}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
package cyclonedx
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/osv-scanner/pkg/lockfile"
|
||||
"github.com/safedep/dry/utils"
|
||||
"github.com/safedep/vet/pkg/common/logger"
|
||||
"github.com/safedep/vet/pkg/parser/custom/packagefile"
|
||||
)
|
||||
|
||||
func Parse(pathToLockfile string) ([]lockfile.PackageDetails, error) {
|
||||
details := []lockfile.PackageDetails{}
|
||||
|
||||
bom := cdx.NewBOM()
|
||||
logger.Infof("Starting SBOM decoding...")
|
||||
|
||||
file, err := os.Open(pathToLockfile)
|
||||
if err != nil {
|
||||
logger.Debugf("Error in Decoding the SBOM file %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
sbom_content := bufio.NewReader(file)
|
||||
decoder := cdx.NewBOMDecoder(sbom_content, cdx.BOMFileFormatJSON)
|
||||
if err = decoder.Decode(bom); err != nil {
|
||||
logger.Debugf("Error in Decoding the SBOM file %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Components is a pointer array and it can be empty
|
||||
components := utils.SafelyGetValue(bom.Components)
|
||||
for _, comp := range components {
|
||||
if d, err := convertSbomComponent2LPD(&comp); err != nil {
|
||||
logger.Debugf("Failed converting sbom to lockfile component: %v", err)
|
||||
} else {
|
||||
details = append(details, *d)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debugf("Found number of packages %d", len(details))
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func convertSbomComponent2LPD(comp *cdx.Component) (*lockfile.PackageDetails, error) {
|
||||
|
||||
pd, err := packagefile.ParsePackageFromPurl(comp.PackageURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pd.CycloneDxRef = comp
|
||||
return pd.Convert2LockfilePackageDetails(), nil
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
package cyclonedx
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/osv-scanner/pkg/lockfile"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseCyclonedxSBOM(t *testing.T) {
|
||||
// Create a sample SBOM JSON file
|
||||
tempFile, _ := ioutil.TempFile("", "sbom_*.json")
|
||||
defer os.Remove(tempFile.Name())
|
||||
sbomContent := `{
|
||||
"Components": [
|
||||
{
|
||||
"group": "",
|
||||
"name": "requests",
|
||||
"version": "1.0",
|
||||
"purl": "pkg:pypi/requests@2.26.0"
|
||||
},
|
||||
{
|
||||
"group": "testgroup",
|
||||
"name": "lodash",
|
||||
"version": "2.0",
|
||||
"purl": "pkg:npm/testgroup/lodash@4.17.21"
|
||||
}
|
||||
]
|
||||
}`
|
||||
err := ioutil.WriteFile(tempFile.Name(), []byte(sbomContent), 0644)
|
||||
assert.Nil(t, err)
|
||||
|
||||
packages, err := Parse(tempFile.Name())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, packages, 2)
|
||||
assert.Equal(t, "requests", packages[0].Name)
|
||||
assert.Equal(t, "testgroup/lodash", packages[1].Name)
|
||||
}
|
||||
|
||||
func TestConvertSbomComponent2LPD(t *testing.T) {
|
||||
component := cdx.Component{
|
||||
Group: "",
|
||||
Name: "requests",
|
||||
Version: "2.26.0",
|
||||
PackageURL: "pkg:pypi/requests@2.26.0",
|
||||
}
|
||||
|
||||
pd, err := convertSbomComponent2LPD(&component)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "requests", pd.Name)
|
||||
assert.Equal(t, "2.26.0", pd.Version)
|
||||
assert.Equal(t, lockfile.PipEcosystem, pd.Ecosystem)
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOM_WithEmptyComponents(t *testing.T) {
|
||||
// Create a sample SBOM JSON file
|
||||
tempFile, _ := ioutil.TempFile("", "sbom_*.json")
|
||||
defer os.Remove(tempFile.Name())
|
||||
sbomContent := `{
|
||||
}`
|
||||
err := ioutil.WriteFile(tempFile.Name(), []byte(sbomContent), 0644)
|
||||
assert.Nil(t, err)
|
||||
|
||||
packages, err := Parse(tempFile.Name())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, packages, 0)
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOM_WithMultipleFiles(t *testing.T) {
|
||||
fixtureDir := "fixtures/cydxsbom"
|
||||
filesToTest := []string{"bom-dpc-int1.json", "bom-du.json", "bom-npm1.json"}
|
||||
|
||||
for _, filename := range filesToTest {
|
||||
t.Run(filename, func(t *testing.T) {
|
||||
filePath := filepath.Join(fixtureDir, filename)
|
||||
|
||||
packages, err := Parse(filePath)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, packages)
|
||||
assert.NotEmpty(t, packages)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,472 +0,0 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:8161abe9-1e8b-4456-ba4f-4d847267b76a",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2023-08-11T06:45:11.368Z",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "cyclonedx",
|
||||
"name": "cdxgen",
|
||||
"version": "8.0.4"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Prabhu Subramanian",
|
||||
"email": "prabhu@appthreat.com"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"group": "",
|
||||
"name": "tmp",
|
||||
"version": "",
|
||||
"type": "application"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "google-cloud-pubsub",
|
||||
"version": "2.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/google-cloud-pubsub@2.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/google-cloud-pubsub@2.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "knowledge-graph",
|
||||
"version": "3.12.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/knowledge-graph@3.12.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/knowledge-graph@3.12.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc_exceptions",
|
||||
"version": "0.4.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-exceptions@0.4.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-exceptions@0.4.3"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc-models",
|
||||
"version": "3.48.6",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-models@3.48.6",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-models@3.48.6"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc-utils",
|
||||
"version": "6.54.32",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-utils@6.54.32",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-utils@6.54.32"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc-convertor",
|
||||
"version": "0.62.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-convertor@0.62.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-convertor@0.62.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc_dorks",
|
||||
"version": "0.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-dorks@0.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-dorks@0.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "deepc_social",
|
||||
"version": "0.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/deepc-social@0.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/deepc-social@0.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "di_client",
|
||||
"version": "1.4.5",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/di-client@1.4.5",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/di-client@1.4.5"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "exploration-events",
|
||||
"version": "3.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/exploration-events@3.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/exploration-events@3.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "nessus-client",
|
||||
"version": "0.13.12",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/nessus-client@0.13.12",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/nessus-client@0.13.12"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "vulners",
|
||||
"version": "1.5.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/vulners@1.5.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/vulners@1.5.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "ipwhois",
|
||||
"version": "1.1.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/ipwhois@1.1.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/ipwhois@1.1.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "gpapi",
|
||||
"version": "0.4.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/gpapi@0.4.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/gpapi@0.4.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "PyGithub",
|
||||
"version": "1.54.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pygithub@1.54.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pygithub@1.54.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "python-whois",
|
||||
"version": "0.7.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/python-whois@0.7.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/python-whois@0.7.3"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "sh",
|
||||
"version": "1.14.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/sh@1.14.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/sh@1.14.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "OTXv2",
|
||||
"version": "1.5.10",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/otxv2@1.5.10",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/otxv2@1.5.10"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "certstream",
|
||||
"version": "1.11",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/certstream@1.11",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/certstream@1.11"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "colorama",
|
||||
"version": "0.4.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/colorama@0.4.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/colorama@0.4.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "ipaddress",
|
||||
"version": "1.0.22",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/ipaddress@1.0.22",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/ipaddress@1.0.22"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "packaging",
|
||||
"version": "19.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/packaging@19.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/packaging@19.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "prettytable",
|
||||
"version": "0.7.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/prettytable@0.7.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/prettytable@0.7.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "pyfiglet",
|
||||
"version": "0.8.post1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pyfiglet@0.8.post1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pyfiglet@0.8.post1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "requests",
|
||||
"version": "2.22.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/requests@2.22.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/requests@2.22.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "termcolor",
|
||||
"version": "1.1.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/termcolor@1.1.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/termcolor@1.1.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "beautifulsoup4",
|
||||
"version": "4.8.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/beautifulsoup4@4.8.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/beautifulsoup4@4.8.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "fcwhispers",
|
||||
"version": "2.1.7",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/fcwhispers@2.1.7",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/fcwhispers@2.1.7"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "gvm-tools",
|
||||
"version": "21.6.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/gvm-tools@21.6.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/gvm-tools@21.6.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "cloud_ip_info",
|
||||
"version": "1.3.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/cloud-ip-info@1.3.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/cloud-ip-info@1.3.3"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "Jinja2",
|
||||
"version": "3.0.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/jinja2@3.0.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/jinja2@3.0.3"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "configobj",
|
||||
"version": "5.0.6",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/configobj@5.0.6",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/configobj@5.0.6"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "cloud_recon",
|
||||
"version": "0.2.7",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/cloud-recon@0.2.7",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/cloud-recon@0.2.7"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "credovergeneric",
|
||||
"version": "1.6.7",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/credovergeneric@1.6.7",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/credovergeneric@1.6.7"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "pycryptodome",
|
||||
"version": "3.12.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pycryptodome@3.12.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pycryptodome@3.12.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "azure-mgmt-resource",
|
||||
"version": "20.0.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/azure-mgmt-resource@20.0.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/azure-mgmt-resource@20.0.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "fc-cloud-storage-client",
|
||||
"version": "0.0.14",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/fc-cloud-storage-client@0.0.14",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/fc-cloud-storage-client@0.0.14"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "azure-identity",
|
||||
"version": "1.7.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/azure-identity@1.7.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/azure-identity@1.7.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "dnsdb",
|
||||
"version": "0.2.5",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/dnsdb@0.2.5",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/dnsdb@0.2.5"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "fc_kb_auth_proxy_client",
|
||||
"version": "0.0.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/fc-kb-auth-proxy-client@0.0.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/fc-kb-auth-proxy-client@0.0.3"
|
||||
}
|
||||
],
|
||||
"services": [],
|
||||
"dependencies": []
|
||||
}
|
||||
@ -1,373 +0,0 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:52d87f2e-93ce-4fd6-96d4-071f97ce61a6",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2023-08-11T04:54:21.340Z",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "cyclonedx",
|
||||
"name": "cdxgen",
|
||||
"version": "8.0.4"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Prabhu Subramanian",
|
||||
"email": "prabhu@appthreat.com"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"group": "",
|
||||
"name": "app",
|
||||
"version": "",
|
||||
"type": "application"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "food-exceptions",
|
||||
"version": "0.4.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/food-exceptions@0.4.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/food-exceptions@0.4.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "food-models",
|
||||
"version": "3.3.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/food-models@3.3.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/food-models@3.3.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "dateutils",
|
||||
"version": "0.6.6",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/dateutils@0.6.6",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/dateutils@0.6.6"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "publicsuffixlist",
|
||||
"version": "0.6.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/publicsuffixlist@0.6.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/publicsuffixlist@0.6.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "dnspython",
|
||||
"version": "1.15.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/dnspython@1.15.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/dnspython@1.15.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "netaddr",
|
||||
"version": "0.7.18",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/netaddr@0.7.18",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/netaddr@0.7.18"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "validators",
|
||||
"version": "0.12.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/validators@0.12.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/validators@0.12.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "fqdn",
|
||||
"version": "1.1.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/fqdn@1.1.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/fqdn@1.1.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "tld",
|
||||
"version": "0.9.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/tld@0.9.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/tld@0.9.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "cchardet",
|
||||
"version": "2.1.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/cchardet@2.1.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/cchardet@2.1.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "urllib3",
|
||||
"version": "1.22",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/urllib3@1.22",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/urllib3@1.22"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "tldextract",
|
||||
"version": "2.2.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/tldextract@2.2.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/tldextract@2.2.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "iptools",
|
||||
"version": "0.7.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/iptools@0.7.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/iptools@0.7.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "parsedatetime",
|
||||
"version": "2.4",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/parsedatetime@2.4",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/parsedatetime@2.4"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "beautifulsoup4",
|
||||
"version": "4.7.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/beautifulsoup4@4.7.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/beautifulsoup4@4.7.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "filetype",
|
||||
"version": "1.0.5",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/filetype@1.0.5",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/filetype@1.0.5"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "pyunpack",
|
||||
"version": "0.1.2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pyunpack@0.1.2",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pyunpack@0.1.2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "patool",
|
||||
"version": "1.12",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/patool@1.12",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/patool@1.12"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "wordninja",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/wordninja@2.0.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/wordninja@2.0.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "iocextract",
|
||||
"version": "1.13.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/iocextract@1.13.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/iocextract@1.13.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "pyparsing",
|
||||
"version": "3.0.8",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pyparsing@3.0.8",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pyparsing@3.0.8"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "titlecase",
|
||||
"version": "0.12.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/titlecase@0.12.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/titlecase@0.12.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "furl",
|
||||
"version": "2.1.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/furl@2.1.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/furl@2.1.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "pathlib2",
|
||||
"version": "2.3.3",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pathlib2@2.3.3",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pathlib2@2.3.3"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "lxml",
|
||||
"version": "4.5.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/lxml@4.5.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/lxml@4.5.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "fuzzywuzzy",
|
||||
"version": "0.18.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/fuzzywuzzy@0.18.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/fuzzywuzzy@0.18.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "PySocks",
|
||||
"version": "1.7.0",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/pysocks@1.7.0",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/pysocks@1.7.0"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "truffleHogRegexes",
|
||||
"version": "0.0.7",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/trufflehogregexes@0.0.7",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/trufflehogregexes@0.0.7"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "",
|
||||
"name": "soupsieve",
|
||||
"version": "1.9.1",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:pypi/soupsieve@1.9.1",
|
||||
"type": "library",
|
||||
"bom-ref": "pkg:pypi/soupsieve@1.9.1"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "actions",
|
||||
"name": "checkout",
|
||||
"version": "v2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:github/actions/checkout@v2",
|
||||
"type": "application",
|
||||
"bom-ref": "pkg:github/actions/checkout@v2"
|
||||
},
|
||||
{
|
||||
"publisher": "",
|
||||
"group": "actions",
|
||||
"name": "setup-python",
|
||||
"version": "v2",
|
||||
"description": "",
|
||||
"licenses": [],
|
||||
"purl": "pkg:github/actions/setup-python@v2",
|
||||
"type": "application",
|
||||
"bom-ref": "pkg:github/actions/setup-python@v2"
|
||||
}
|
||||
],
|
||||
"services": [],
|
||||
"dependencies": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
136
pkg/parser/cyclonedx.go
Normal file
136
pkg/parser/cyclonedx.go
Normal file
@ -0,0 +1,136 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/safedep/dry/utils"
|
||||
"github.com/safedep/vet/pkg/common/logger"
|
||||
"github.com/safedep/vet/pkg/common/purl"
|
||||
"github.com/safedep/vet/pkg/models"
|
||||
)
|
||||
|
||||
func parseSbomCycloneDxAsGraph(path string, config *ParserConfig) (*models.PackageManifest, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
bom := cdx.NewBOM()
|
||||
bomReader := bufio.NewReader(file)
|
||||
|
||||
format := cdx.BOMFileFormatJSON
|
||||
if len(path) > 4 && path[len(path)-4:] == ".xml" {
|
||||
format = cdx.BOMFileFormatXML
|
||||
}
|
||||
|
||||
decoder := cdx.NewBOMDecoder(bomReader, format)
|
||||
if err = decoder.Decode(bom); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fail fast if the BOM does not have the main (app) component
|
||||
if bom.Metadata == nil || bom.Metadata.Component == nil {
|
||||
return nil, fmt.Errorf("Invalid CycloneDX SBOM: Metadata or Component is nil")
|
||||
}
|
||||
|
||||
// Maintain a cache of BOM / packageUrl ref to package mapping for re-use while adding
|
||||
// dependency relations
|
||||
bomRefMap := make(map[string]*models.Package)
|
||||
|
||||
// Lets start by adding the main component
|
||||
ref, pkg, err := cdxExtractPackageFromComponent(*bom.Metadata.Component)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract main package from component: %v", err)
|
||||
}
|
||||
|
||||
bomRefMap[ref] = pkg
|
||||
|
||||
manifest := models.NewPackageManifest(path, models.EcosystemCyDxSBOM)
|
||||
components := utils.SafelyGetValue(bom.Components)
|
||||
|
||||
// Iterate over all components in the BOM and add the package in dependency graph
|
||||
// This just adds the nodes in the graph without any relations
|
||||
for _, component := range components {
|
||||
ref, pkg, err := cdxExtractPackageFromComponent(component)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to extract package from component %v: %v",
|
||||
component, err)
|
||||
continue
|
||||
}
|
||||
|
||||
bomRefMap[ref] = pkg
|
||||
manifest.AddPackage(pkg)
|
||||
}
|
||||
|
||||
// Iterate over the dependency relations and add the edges in the graph
|
||||
depedencyRelations := utils.SafelyGetValue(bom.Dependencies)
|
||||
for _, dependencyRelation := range depedencyRelations {
|
||||
// We must have seen the package while enumerating components without which
|
||||
// we cannot add a dependency relation
|
||||
pkg, ok := bomRefMap[dependencyRelation.Ref]
|
||||
if !ok {
|
||||
logger.Errorf("Dependency ref: %s not found in bomRefMap", dependencyRelation.Ref)
|
||||
continue
|
||||
}
|
||||
|
||||
// We lookup the package in the bomRefMap and add the dependency relation
|
||||
// We fail if we cannot find the package because as per CycloneDX spec it seems
|
||||
// every known component must be defined.
|
||||
dependencies := utils.SafelyGetValue(dependencyRelation.Dependencies)
|
||||
for _, dependency := range dependencies {
|
||||
dependsOnPkg, ok := bomRefMap[dependency]
|
||||
if !ok {
|
||||
logger.Errorf("%s depends on %s which is not found in bomRefMap",
|
||||
dependencyRelation.Ref, dependency)
|
||||
continue
|
||||
}
|
||||
|
||||
if cdxIsMainComponent(bom, dependencyRelation.Ref) {
|
||||
manifest.DependencyGraph.AddRootNode(dependsOnPkg)
|
||||
} else {
|
||||
manifest.DependencyGraph.AddDependency(pkg, dependsOnPkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Infof("Resolved %d packages as graph from BOM: %s",
|
||||
len(manifest.GetPackages()), path)
|
||||
|
||||
// We consider that a dependency graph is constructed from BOM
|
||||
// only when we find at least 1 dependency relation.
|
||||
if len(depedencyRelations) > 0 {
|
||||
manifest.DependencyGraph.SetPresent(true)
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
func cdxIsMainComponent(bom *cdx.BOM, ref string) bool {
|
||||
return bom.Metadata != nil && bom.Metadata.Component != nil &&
|
||||
(bom.Metadata.Component.PackageURL == ref || bom.Metadata.Component.BOMRef == ref)
|
||||
}
|
||||
|
||||
func cdxExtractPackageFromComponent(component cdx.Component) (string, *models.Package, error) {
|
||||
pUrl := component.PackageURL
|
||||
if pUrl == "" {
|
||||
pUrl = component.BOMRef
|
||||
}
|
||||
|
||||
if pUrl == "" {
|
||||
return "", nil, fmt.Errorf("Invalid CycloneDX SBOM: PackageURL or BOMRef is nil")
|
||||
}
|
||||
|
||||
parsedPurl, err := purl.ParsePackageUrl(pUrl)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return pUrl, &models.Package{
|
||||
PackageDetails: parsedPurl.GetPackageDetails(),
|
||||
}, nil
|
||||
}
|
||||
149
pkg/parser/cyclonedx_test.go
Normal file
149
pkg/parser/cyclonedx_test.go
Normal file
@ -0,0 +1,149 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/osv-scanner/pkg/lockfile"
|
||||
"github.com/safedep/vet/pkg/common/purl"
|
||||
"github.com/safedep/vet/pkg/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseCyclonedxSBOM(t *testing.T) {
|
||||
tempFile, _ := os.CreateTemp("", "sbom_*.json")
|
||||
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer tempFile.Close()
|
||||
|
||||
sbomContent := `{
|
||||
"bomFormat": "CycloneDX",
|
||||
"metadata": {
|
||||
"component": {
|
||||
"name": "mybigapp",
|
||||
"version": "2.26.0",
|
||||
"type": "application",
|
||||
"purl": "pkg:pypi/mybigapp@2.26.0"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"group": "",
|
||||
"name": "requests",
|
||||
"version": "1.0",
|
||||
"purl": "pkg:pypi/requests@2.26.0"
|
||||
},
|
||||
{
|
||||
"group": "testgroup",
|
||||
"name": "lodash",
|
||||
"version": "2.0",
|
||||
"purl": "pkg:npm/testgroup/lodash@4.17.21"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
err := os.WriteFile(tempFile.Name(), []byte(sbomContent), 0644)
|
||||
assert.Nil(t, err)
|
||||
|
||||
manifest, err := parseSbomCycloneDxAsGraph(tempFile.Name(), &ParserConfig{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
packages := manifest.GetPackages()
|
||||
|
||||
assert.Equal(t, manifest.GetDisplayPath(), tempFile.Name())
|
||||
assert.Len(t, packages, 2)
|
||||
|
||||
b := slices.ContainsFunc(packages, func(pkg *models.Package) bool {
|
||||
return pkg.GetName() == "requests" &&
|
||||
pkg.GetVersion() == "2.26.0"
|
||||
})
|
||||
|
||||
assert.True(t, b)
|
||||
|
||||
b = slices.ContainsFunc(packages, func(pkg *models.Package) bool {
|
||||
return pkg.GetName() == "testgroup/lodash" &&
|
||||
pkg.GetVersion() == "4.17.21"
|
||||
})
|
||||
|
||||
assert.True(t, b)
|
||||
}
|
||||
|
||||
func TestConvertSbomComponentToPackage(t *testing.T) {
|
||||
component := cdx.Component{
|
||||
Group: "",
|
||||
Name: "requests",
|
||||
Version: "2.26.0",
|
||||
PackageURL: "pkg:pypi/requests@2.26.0",
|
||||
}
|
||||
|
||||
ref, pd, err := cdxExtractPackageFromComponent(component)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, component.PackageURL, ref)
|
||||
assert.Equal(t, "requests", pd.Name)
|
||||
assert.Equal(t, "2.26.0", pd.Version)
|
||||
assert.Equal(t, lockfile.PipEcosystem, pd.Ecosystem)
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOMWithEmptyComponents(t *testing.T) {
|
||||
tempFile, _ := os.CreateTemp("", "sbom_*.json")
|
||||
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer tempFile.Close()
|
||||
|
||||
sbomContent := `{}`
|
||||
err := os.WriteFile(tempFile.Name(), []byte(sbomContent), 0644)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parseSbomCycloneDxAsGraph(tempFile.Name(), &ParserConfig{})
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOMWithGradleSBOM(t *testing.T) {
|
||||
manifest, err := parseSbomCycloneDxAsGraph("./fixtures/bom-maven.json", &ParserConfig{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, manifest)
|
||||
assert.NotEmpty(t, manifest.GetPackages())
|
||||
|
||||
dg := manifest.DependencyGraph
|
||||
assert.NotEmpty(t, dg.GetNodes())
|
||||
assert.NotEmpty(t, dg.GetPackages())
|
||||
|
||||
pkg, err := purl.ParsePackageUrl("pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.0?type=jar")
|
||||
assert.Nil(t, err)
|
||||
|
||||
nodes := dg.GetDependencies(&models.Package{PackageDetails: pkg.GetPackageDetails()})
|
||||
assert.Equal(t, 2, len(nodes))
|
||||
|
||||
assert.Equal(t, "com.fasterxml.jackson.core:jackson-annotations", nodes[0].GetName())
|
||||
assert.Equal(t, "com.fasterxml.jackson.core:jackson-core", nodes[1].GetName())
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOMWithMavenSBOM(t *testing.T) {
|
||||
manifest, err := parseSbomCycloneDxAsGraph("./fixtures/bom-dropwizard-cdx-example.json", &ParserConfig{})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, manifest)
|
||||
|
||||
packages := manifest.GetPackages()
|
||||
assert.Equal(t, 167, len(packages))
|
||||
|
||||
pkg, err := purl.ParsePackageUrl("pkg:maven/io.dropwizard/dropwizard-client@1.3.15?type=jar")
|
||||
assert.Nil(t, err)
|
||||
|
||||
nodes := manifest.DependencyGraph.GetDependencies(&models.Package{PackageDetails: pkg.GetPackageDetails()})
|
||||
assert.Equal(t, 5, len(nodes))
|
||||
|
||||
nodes = manifest.DependencyGraph.PathToRoot(&models.Package{PackageDetails: pkg.GetPackageDetails()})
|
||||
assert.Equal(t, 1, len(nodes))
|
||||
}
|
||||
|
||||
func TestParseCyclonedxSBOMWithNpmSBOM(t *testing.T) {
|
||||
manifest, err := parseSbomCycloneDxAsGraph("./fixtures/bom-juiceshop-cdx-example.json", &ParserConfig{})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, manifest)
|
||||
|
||||
assert.Equal(t, 840, len(manifest.GetPackages()))
|
||||
}
|
||||
10621
pkg/parser/fixtures/bom-dropwizard-cdx-example.json
Normal file
10621
pkg/parser/fixtures/bom-dropwizard-cdx-example.json
Normal file
File diff suppressed because it is too large
Load Diff
29010
pkg/parser/fixtures/bom-juiceshop-cdx-example.json
Normal file
29010
pkg/parser/fixtures/bom-juiceshop-cdx-example.json
Normal file
File diff suppressed because it is too large
Load Diff
790
pkg/parser/fixtures/bom-maven.json
Normal file
790
pkg/parser/fixtures/bom-maven.json
Normal file
File diff suppressed because one or more lines are too long
@ -9,7 +9,6 @@ import (
|
||||
"github.com/safedep/vet/pkg/models"
|
||||
|
||||
"github.com/safedep/vet/pkg/parser/custom/py"
|
||||
cdx "github.com/safedep/vet/pkg/parser/custom/sbom/cyclonedx"
|
||||
"github.com/safedep/vet/pkg/parser/custom/sbom/spdx"
|
||||
)
|
||||
|
||||
@ -42,10 +41,9 @@ var supportedEcosystems map[string]bool = map[string]bool{
|
||||
|
||||
// TODO: Migrate these to graph parser
|
||||
var customExperimentalParsers map[string]lockfile.PackageDetailsParser = map[string]lockfile.PackageDetailsParser{
|
||||
customParserTypePyWheel: parsePythonWheelDist,
|
||||
customParserCycloneDXSBOM: cdx.Parse,
|
||||
customParserSpdxSBOM: spdx.Parse,
|
||||
customParserTypeSetupPy: py.ParseSetuppy,
|
||||
customParserTypePyWheel: parsePythonWheelDist,
|
||||
customParserSpdxSBOM: spdx.Parse,
|
||||
customParserTypeSetupPy: py.ParseSetuppy,
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
@ -73,7 +71,8 @@ type dependencyGraphParser func(lockfilePath string, config *ParserConfig) (*mod
|
||||
|
||||
// Maintain a map of lockfileAs to dependencyGraphParser
|
||||
var dependencyGraphParsers map[string]dependencyGraphParser = map[string]dependencyGraphParser{
|
||||
"package-lock.json": parseNpmPackageLockAsGraph,
|
||||
"package-lock.json": parseNpmPackageLockAsGraph,
|
||||
customParserCycloneDXSBOM: parseSbomCycloneDxAsGraph,
|
||||
}
|
||||
|
||||
func List(experimental bool) []string {
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestListParser(t *testing.T) {
|
||||
parsers := List(false)
|
||||
assert.Equal(t, 12, len(parsers))
|
||||
assert.Equal(t, 13, len(parsers))
|
||||
}
|
||||
|
||||
func TestInvalidEcosystemMapping(t *testing.T) {
|
||||
|
||||
@ -80,6 +80,9 @@ func (r *DotGraphReporter) dotRenderDependencyGraph(dg *models.DependencyGraph[*
|
||||
sb.WriteString(" rankdir=LR;\n")
|
||||
sb.WriteString(" node [shape=box];\n")
|
||||
|
||||
// Add a dummy root node
|
||||
sb.WriteString(" \"root\";\n")
|
||||
|
||||
// Generate the node names
|
||||
for _, node := range dg.GetNodes() {
|
||||
sb.WriteString(" ")
|
||||
@ -89,6 +92,14 @@ func (r *DotGraphReporter) dotRenderDependencyGraph(dg *models.DependencyGraph[*
|
||||
|
||||
// Add the relations
|
||||
for _, node := range dg.GetNodes() {
|
||||
if node.Root {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString("\"root\"")
|
||||
sb.WriteString(" -> ")
|
||||
sb.WriteString("\"" + r.nodeNameForPackage(node.Data) + "\"")
|
||||
sb.WriteString(";\n")
|
||||
}
|
||||
|
||||
for _, edge := range node.Children {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString("\"" + r.nodeNameForPackage(node.Data) + "\"")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user