From bc35345afb7d95de316720232f7ca93074aa1319 Mon Sep 17 00:00:00 2001 From: William Murphy Date: Thu, 14 Nov 2024 16:45:00 -0500 Subject: [PATCH] feat: emit dependency relationships found in Cargo.lock (#3443) * feat: emit dependency relationships found in Cargo.lock Include updating test Cargo.lock to have dependencies on multiple versions of the same crate. Signed-off-by: Will Murphy --- syft/pkg/cataloger/rust/parse_cargo_lock.go | 41 +- .../cataloger/rust/parse_cargo_lock_test.go | 441 ++++++++++++------ .../cataloger/rust/test-fixtures/Cargo.lock | 30 ++ 3 files changed, 352 insertions(+), 160 deletions(-) diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock.go b/syft/pkg/cataloger/rust/parse_cargo_lock.go index 01e9fd87e0a6..d800eec7a747 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock.go @@ -33,19 +33,50 @@ func parseCargoLock(_ context.Context, _ file.Resolver, _ *generic.Environment, } var pkgs []pkg.Package + pkgIndex := make(map[string]int) for _, p := range m.Packages { if p.Dependencies == nil { p.Dependencies = make([]string, 0) } + newPkg := newPackageFromCargoMetadata( + p, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) pkgs = append( pkgs, - newPackageFromCargoMetadata( - p, - reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), - ), + newPkg, ) + newIx := len(pkgs) - 1 + // Cargo.lock dependencies are strings that are the name of a package, if that + // is unambiguous, or a string like "name version" if the name alone is not + // ambiguous. Set both keys in the map, since we don't know which key is + // going to be used until we're trying to resolve dependencies. If the + // first key is overwritten, that means the package name was an ambiguous dependency + // and "name version" will be used as the key anyway. + keys := []string{ + newPkg.Name, + fmt.Sprintf("%s %s", newPkg.Name, newPkg.Version), + } + for _, k := range keys { + pkgIndex[k] = newIx + } + } + var relationships []artifact.Relationship + for _, p := range pkgs { + meta := p.Metadata.(pkg.RustCargoLockEntry) + for _, d := range meta.Dependencies { + i, ok := pkgIndex[d] + if !ok { + continue + } + relationships = append(relationships, artifact.Relationship{ + From: p, + To: pkgs[i], + Type: artifact.DependencyOfRelationship, + }) + } } - return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages") + return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages") } diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go index 4239297795d1..2d7d2b4b639f 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go @@ -12,184 +12,315 @@ import ( func TestParseCargoLock(t *testing.T) { fixture := "test-fixtures/Cargo.lock" locations := file.NewLocationSet(file.NewLocation(fixture)) - expectedPkgs := []pkg.Package{ - { - Name: "ansi_term", - Version: "0.12.1", - PURL: "pkg:cargo/ansi_term@0.12.1", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "ansi_term", - Version: "0.12.1", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2", - Dependencies: []string{ - "winapi", - }, + ansiTerm := pkg.Package{ + Name: "ansi_term", + Version: "0.12.1", + PURL: "pkg:cargo/ansi_term@0.12.1", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "ansi_term", + Version: "0.12.1", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2", + Dependencies: []string{ + "winapi", }, }, - { - Name: "matches", - Version: "0.1.8", - PURL: "pkg:cargo/matches@0.1.8", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "matches", - Version: "0.1.8", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08", - Dependencies: []string{}, + } + errno := pkg.Package{ + Name: "errno", + Version: "0.3.9", + PURL: "pkg:cargo/errno@0.3.9", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "errno", + Version: "0.3.9", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba", + Dependencies: []string{ + "windows-sys 0.52.0", }, }, - { - Name: "memchr", - Version: "2.3.3", - PURL: "pkg:cargo/memchr@2.3.3", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "memchr", - Version: "2.3.3", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400", - Dependencies: []string{}, + } + matches := pkg.Package{ + Name: "matches", + Version: "0.1.8", + PURL: "pkg:cargo/matches@0.1.8", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "matches", + Version: "0.1.8", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08", + Dependencies: []string{}, + }, + } + memchr := pkg.Package{ + Name: "memchr", + Version: "2.3.3", + PURL: "pkg:cargo/memchr@2.3.3", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "memchr", + Version: "2.3.3", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400", + Dependencies: []string{}, + }, + } + + natord := pkg.Package{ + Name: "natord", + Version: "1.0.9", + PURL: "pkg:cargo/natord@1.0.9", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "natord", + Version: "1.0.9", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c", + Dependencies: []string{}, + }, + } + + nom := pkg.Package{ + Name: "nom", + Version: "4.2.3", + PURL: "pkg:cargo/nom@4.2.3", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "nom", + Version: "4.2.3", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6", + Dependencies: []string{ + "memchr", + "version_check", }, }, - { - Name: "natord", - Version: "1.0.9", - PURL: "pkg:cargo/natord@1.0.9", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "natord", - Version: "1.0.9", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c", - Dependencies: []string{}, + } + schannel := pkg.Package{ + Name: "schannel", + Version: "0.1.26", + PURL: "pkg:cargo/schannel@0.1.26", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "schannel", + Version: "0.1.26", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1", + Dependencies: []string{ + "windows-sys 0.59.0", }, }, - { - Name: "nom", - Version: "4.2.3", - PURL: "pkg:cargo/nom@4.2.3", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "nom", - Version: "4.2.3", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6", - Dependencies: []string{ - "memchr", - "version_check", - }, + } + + unicodeBidi := pkg.Package{ + Name: "unicode-bidi", + Version: "0.3.4", + PURL: "pkg:cargo/unicode-bidi@0.3.4", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "unicode-bidi", + Version: "0.3.4", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5", + Dependencies: []string{ + "matches", + "bogus", // a bad dependency to test error handling }, }, - { - Name: "unicode-bidi", - Version: "0.3.4", - PURL: "pkg:cargo/unicode-bidi@0.3.4", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "unicode-bidi", - Version: "0.3.4", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5", - Dependencies: []string{ - "matches", - }, + } + + versionCheck := pkg.Package{ + Name: "version_check", + Version: "0.1.5", + PURL: "pkg:cargo/version_check@0.1.5", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "version_check", + Version: "0.1.5", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd", + Dependencies: []string{}, + }, + } + + winapi := pkg.Package{ + Name: "winapi", + Version: "0.3.9", + PURL: "pkg:cargo/winapi@0.3.9", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "winapi", + Version: "0.3.9", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419", + Dependencies: []string{ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", }, }, + } + + winAPIi686PCWindowsGNU := pkg.Package{ + Name: "winapi-i686-pc-windows-gnu", + Version: "0.4.0", + PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "winapi-i686-pc-windows-gnu", + Version: "0.4.0", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", + Dependencies: []string{}, + }, + } + + winAPIx8664PCWindowsGNU := pkg.Package{ + Name: "winapi-x86_64-pc-windows-gnu", + Version: "0.4.0", + PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "winapi-x86_64-pc-windows-gnu", + Version: "0.4.0", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", + Dependencies: []string{}, + }, + } + + windowsSys52 := pkg.Package{ + Name: "windows-sys", + Version: "0.52.0", + PURL: "pkg:cargo/windows-sys@0.52.0", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "windows-sys", + Version: "0.52.0", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d", + Dependencies: []string{}, + }, + } + + windowsSys59 := pkg.Package{ + Name: "windows-sys", + Version: "0.59.0", + PURL: "pkg:cargo/windows-sys@0.59.0", + Locations: locations, + Language: pkg.Rust, + Type: pkg.RustPkg, + Licenses: pkg.NewLicenseSet(), + Metadata: pkg.RustCargoLockEntry{ + Name: "windows-sys", + Version: "0.59.0", + Source: "registry+https://github.com/rust-lang/crates.io-index", + Checksum: "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b", + Dependencies: []string{}, + }, + } + expectedPkgs := []pkg.Package{ + ansiTerm, + errno, + matches, + memchr, + natord, + nom, + schannel, + unicodeBidi, + versionCheck, + winapi, + winAPIi686PCWindowsGNU, + winAPIx8664PCWindowsGNU, + windowsSys52, + windowsSys59, + } + + expectedRelationships := []artifact.Relationship{ { - Name: "version_check", - Version: "0.1.5", - PURL: "pkg:cargo/version_check@0.1.5", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "version_check", - Version: "0.1.5", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd", - Dependencies: []string{}, - }, + From: ansiTerm, + To: winapi, + Type: artifact.DependencyOfRelationship, }, { - Name: "winapi", - Version: "0.3.9", - PURL: "pkg:cargo/winapi@0.3.9", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "winapi", - Version: "0.3.9", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419", - Dependencies: []string{ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", - }, - }, + From: errno, + To: windowsSys52, + Type: artifact.DependencyOfRelationship, }, { - Name: "winapi-i686-pc-windows-gnu", - Version: "0.4.0", - PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "winapi-i686-pc-windows-gnu", - Version: "0.4.0", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", - Dependencies: []string{}, - }, + From: nom, + To: memchr, + Type: artifact.DependencyOfRelationship, }, { - Name: "winapi-x86_64-pc-windows-gnu", - Version: "0.4.0", - PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0", - Locations: locations, - Language: pkg.Rust, - Type: pkg.RustPkg, - Licenses: pkg.NewLicenseSet(), - Metadata: pkg.RustCargoLockEntry{ - Name: "winapi-x86_64-pc-windows-gnu", - Version: "0.4.0", - Source: "registry+https://github.com/rust-lang/crates.io-index", - Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", - Dependencies: []string{}, - }, + From: nom, + To: versionCheck, + Type: artifact.DependencyOfRelationship, + }, + { + From: schannel, + To: windowsSys59, + Type: artifact.DependencyOfRelationship, + }, + { + From: unicodeBidi, + To: matches, + Type: artifact.DependencyOfRelationship, + }, + { + From: winapi, + To: winAPIi686PCWindowsGNU, + Type: artifact.DependencyOfRelationship, + }, + { + From: winapi, + To: winAPIx8664PCWindowsGNU, + Type: artifact.DependencyOfRelationship, }, } - // TODO: no relationships are under test yet - var expectedRelationships []artifact.Relationship - pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships) - } func Test_corruptCargoLock(t *testing.T) { diff --git a/syft/pkg/cataloger/rust/test-fixtures/Cargo.lock b/syft/pkg/cataloger/rust/test-fixtures/Cargo.lock index 5ee8fb7386fd..04028e78fd48 100644 --- a/syft/pkg/cataloger/rust/test-fixtures/Cargo.lock +++ b/syft/pkg/cataloger/rust/test-fixtures/Cargo.lock @@ -9,6 +9,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "matches" version = "0.1.8" @@ -37,6 +46,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -44,6 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" dependencies = [ "matches", + "bogus", # not present in cargo.lock; test error handling ] [[package]] @@ -74,3 +93,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"