From b95b8b1ee885ffc9312b0674a5650d1ebfd3fdb9 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Mon, 1 Jan 2024 14:47:04 +0200 Subject: [PATCH 1/2] conan lock 2.x requires field support Signed-off-by: houdini91 --- syft/pkg/cataloger/cpp/package.go | 4 ++ syft/pkg/cataloger/cpp/parse_conanlock.go | 80 ++++++++++++++++++++++- syft/pkg/conan.go | 12 ++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/syft/pkg/cataloger/cpp/package.go b/syft/pkg/cataloger/cpp/package.go index 9a0b4be1b45..c3e3445a4ed 100644 --- a/syft/pkg/cataloger/cpp/package.go +++ b/syft/pkg/cataloger/cpp/package.go @@ -74,6 +74,10 @@ func newConanlockPackage(m pkg.ConanLockEntry, locations ...file.Location) *pkg. return newConanPackage(m.Ref, m, locations...) } +func newConanRefrencePackage(m pkg.ConanReference, locations ...file.Location) *pkg.Package { + return newConanPackage(m.Ref, m, locations...) +} + func newConaninfoPackage(m pkg.ConaninfoEntry, locations ...file.Location) *pkg.Package { return newConanPackage(m.Ref, m, locations...) } diff --git a/syft/pkg/cataloger/cpp/parse_conanlock.go b/syft/pkg/cataloger/cpp/parse_conanlock.go index 80a403ea9a6..492ab273ceb 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock.go @@ -25,8 +25,13 @@ type conanLock struct { Path string `json:"path"` } `json:"nodes"` } `json:"graph_lock"` - Version string `json:"version"` - ProfileHost string `json:"profile_host"` + Version string `json:"version"` + ProfileHost string `json:"profile_host"` + ProfileBuild string `json:"profile_build,omitempty"` + // conan v0.5+ lockfiles use "requires", "build_requires" and "python_requires" + Requires []string `json:"requires,omitempty"` + BuildRequires []string `json:"build_requires,omitempty"` + PythonRequires []string `json:"python_requires,omitempty"` } // parseConanlock is a parser function for conan.lock contents, returning all packages discovered. @@ -41,6 +46,26 @@ func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.Locatio // in a second iteration var indexToPkgMap = map[string]pkg.Package{} + // Support for conan lock 2.x requires field + for _, ref := range cl.Requires { + reference := parseConanRenference(ref) + if reference.Name == "" { + continue + } + + p := newConanRefrencePackage( + reference, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) + + if p != nil { + pk := *p + pkgs = append(pkgs, pk) + indexToPkgMap[reference.Name] = pk + } + + } + // we do not want to store the index list requires in the conan metadata, because it is not useful to have it in // the SBOM. Instead, we will store it in a map and then use it to build the relationships // maps pkg.ID to a list of indices @@ -102,3 +127,54 @@ func parseOptions(options string) map[string]string { return o } + +func parseConanRenference(ref string) pkg.ConanReference { + // very flexible format name/version[@username[/channel]][#rrev][:pkgid[#prev]][%timestamp] + reference := pkg.ConanReference{Ref: ref} + + parts := strings.SplitN(ref, "%", 2) + if len(parts) == 2 { + ref = parts[0] + reference.TimeStamp = parts[1] + } + + parts = strings.SplitN(ref, ":", 2) + if len(parts) == 2 { + ref = parts[0] + parts = strings.SplitN(parts[1], "#", 2) + reference.PackageID = parts[0] + if len(parts) == 2 { + reference.PackageRevision = parts[1] + } + } + + parts = strings.SplitN(ref, "#", 2) + if len(parts) == 2 { + ref = parts[0] + reference.RecipeRevision = parts[1] + } + + parts = strings.SplitN(ref, "@", 2) + if len(parts) == 2 { + ref = parts[0] + UsernameChannel := parts[1] + + parts = strings.SplitN(UsernameChannel, "/", 2) + reference.Username = parts[0] + if len(parts) == 2 { + reference.Channel = parts[1] + } + } + + parts = strings.SplitN(ref, "/", 2) + if len(parts) == 2 { + reference.Name = parts[0] + reference.Version = parts[1] + } else { + // consumer conanfile.txt or conanfile.py might not have a name + reference.Name = "" + reference.Version = ref + } + + return reference +} diff --git a/syft/pkg/conan.go b/syft/pkg/conan.go index bf2b1584b4f..a76b7e8d9e9 100644 --- a/syft/pkg/conan.go +++ b/syft/pkg/conan.go @@ -1,5 +1,17 @@ package pkg +type ConanReference struct { + Name string + Version string + Username string + Channel string + RecipeRevision string + PackageID string + PackageRevision string + TimeStamp string + Ref string +} + // ConanLockEntry represents a single "node" entry from a conan.lock file. type ConanLockEntry struct { Ref string `json:"ref"` From 3b18dd47fdd98828718991223bc1484da46ce898 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Tue, 16 Jan 2024 17:40:41 +0200 Subject: [PATCH 2/2] PR review, struct renaming Signed-off-by: houdini91 --- syft/internal/packagemetadata/generated.go | 2 +- syft/internal/packagemetadata/names.go | 2 +- syft/internal/packagemetadata/names_test.go | 4 ++-- syft/pkg/cataloger/cpp/package.go | 4 ++-- syft/pkg/cataloger/cpp/parse_conanlock.go | 8 +++---- .../pkg/cataloger/cpp/parse_conanlock_test.go | 12 +++++----- syft/pkg/conan.go | 24 +++++++++---------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index d2e119c226e..07f275b43e2 100644 --- a/syft/internal/packagemetadata/generated.go +++ b/syft/internal/packagemetadata/generated.go @@ -11,7 +11,7 @@ func AllTypes() []any { pkg.ApkDBEntry{}, pkg.BinarySignature{}, pkg.CocoaPodfileLockEntry{}, - pkg.ConanLockEntry{}, + pkg.ConanV1LockEntry{}, pkg.ConanfileEntry{}, pkg.ConaninfoEntry{}, pkg.DartPubspecLockEntry{}, diff --git a/syft/internal/packagemetadata/names.go b/syft/internal/packagemetadata/names.go index 2bec9866937..a346f7cc6a4 100644 --- a/syft/internal/packagemetadata/names.go +++ b/syft/internal/packagemetadata/names.go @@ -66,7 +66,7 @@ var jsonTypes = makeJSONTypes( jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"), jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"), jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"), - jsonNames(pkg.ConanLockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"), + jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"), jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"), jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"), jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"), diff --git a/syft/internal/packagemetadata/names_test.go b/syft/internal/packagemetadata/names_test.go index 21ee46ff734..752cb7c82e0 100644 --- a/syft/internal/packagemetadata/names_test.go +++ b/syft/internal/packagemetadata/names_test.go @@ -103,7 +103,7 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) { { name: "map pkg.ConanLockEntry struct type", input: "ConanLockMetadataType", - expected: reflect.TypeOf(pkg.ConanLockEntry{}), + expected: reflect.TypeOf(pkg.ConanV1LockEntry{}), }, { name: "map pkg.ConanfileEntry struct type", @@ -290,7 +290,7 @@ func Test_JSONName_JSONLegacyName(t *testing.T) { }, { name: "ConanLockMetadata", - metadata: pkg.ConanLockEntry{}, + metadata: pkg.ConanV1LockEntry{}, expectedJSONName: "c-conan-lock-entry", expectedLegacyName: "ConanLockMetadataType", }, diff --git a/syft/pkg/cataloger/cpp/package.go b/syft/pkg/cataloger/cpp/package.go index c3e3445a4ed..1b29329afa6 100644 --- a/syft/pkg/cataloger/cpp/package.go +++ b/syft/pkg/cataloger/cpp/package.go @@ -70,11 +70,11 @@ func newConanfilePackage(m pkg.ConanfileEntry, locations ...file.Location) *pkg. return newConanPackage(m.Ref, m, locations...) } -func newConanlockPackage(m pkg.ConanLockEntry, locations ...file.Location) *pkg.Package { +func newConanlockPackage(m pkg.ConanV1LockEntry, locations ...file.Location) *pkg.Package { return newConanPackage(m.Ref, m, locations...) } -func newConanRefrencePackage(m pkg.ConanReference, locations ...file.Location) *pkg.Package { +func newConanRefrencePackage(m pkg.ConanV2LockEntry, locations ...file.Location) *pkg.Package { return newConanPackage(m.Ref, m, locations...) } diff --git a/syft/pkg/cataloger/cpp/parse_conanlock.go b/syft/pkg/cataloger/cpp/parse_conanlock.go index 492ab273ceb..4b270f6c12b 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock.go @@ -48,7 +48,7 @@ func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.Locatio // Support for conan lock 2.x requires field for _, ref := range cl.Requires { - reference := parseConanRenference(ref) + reference := parseConanV2Reference(ref) if reference.Name == "" { continue } @@ -72,7 +72,7 @@ func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.Locatio var parsedPkgRequires = map[artifact.ID][]string{} for idx, node := range cl.GraphLock.Nodes { - metadata := pkg.ConanLockEntry{ + metadata := pkg.ConanV1LockEntry{ Ref: node.Ref, Options: parseOptions(node.Options), Path: node.Path, @@ -128,9 +128,9 @@ func parseOptions(options string) map[string]string { return o } -func parseConanRenference(ref string) pkg.ConanReference { +func parseConanV2Reference(ref string) pkg.ConanV2LockEntry { // very flexible format name/version[@username[/channel]][#rrev][:pkgid[#prev]][%timestamp] - reference := pkg.ConanReference{Ref: ref} + reference := pkg.ConanV2LockEntry{Ref: ref} parts := strings.SplitN(ref, "%", 2) if len(parts) == 2 { diff --git a/syft/pkg/cataloger/cpp/parse_conanlock_test.go b/syft/pkg/cataloger/cpp/parse_conanlock_test.go index a068978884f..92d8db67662 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock_test.go @@ -19,7 +19,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "mfast/1.2.2@my_user/my_channel#c6f6387c9b99780f0ee05e25f99d0f39", Options: map[string]string{ "fPIC": "True", @@ -110,7 +110,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "boost/1.75.0#a9c318f067216f900900e044e7af4ab1", Options: map[string]string{ "addr2line_location": "/usr/bin/addr2line", @@ -196,7 +196,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "zlib/1.2.12#c67ce17f2e96b972d42393ce50a76a1a", Options: map[string]string{ "fPIC": "True", @@ -214,7 +214,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "bzip2/1.0.8#62a8031289639043797cf53fa876d0ef", Options: map[string]string{ "build_executable": "True", @@ -233,7 +233,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "libbacktrace/cci.20210118#76e40b760e0bcd602d46db56b22820ab", Options: map[string]string{ "fPIC": "True", @@ -251,7 +251,7 @@ func TestParseConanlock(t *testing.T) { Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, - Metadata: pkg.ConanLockEntry{ + Metadata: pkg.ConanV1LockEntry{ Ref: "tinyxml2/9.0.0#9f13a36ebfc222cd55fe531a0a8d94d1", Options: map[string]string{ "fPIC": "True", diff --git a/syft/pkg/conan.go b/syft/pkg/conan.go index a76b7e8d9e9..23f1e3c3815 100644 --- a/syft/pkg/conan.go +++ b/syft/pkg/conan.go @@ -1,19 +1,19 @@ package pkg -type ConanReference struct { - Name string - Version string - Username string - Channel string - RecipeRevision string - PackageID string - PackageRevision string - TimeStamp string - Ref string +type ConanV2LockEntry struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Username string `json:"username,omitempty"` + Channel string `json:"channel,omitempty"` + RecipeRevision string `json:"recipe_revision,omitempty"` + PackageID string `json:"package_id,omitempty"` + PackageRevision string `json:"package_revision,omitempty"` + TimeStamp string `json:"timestamp,omitempty"` + Ref string `json:"ref"` } -// ConanLockEntry represents a single "node" entry from a conan.lock file. -type ConanLockEntry struct { +// ConanV1LockEntry represents a single "node" entry from a conan.lock file. +type ConanV1LockEntry struct { Ref string `json:"ref"` PackageID string `json:"package_id,omitempty"` Prev string `json:"prev,omitempty"`