From 5d156b82417cd01db5fe521af880a75cf2ef3ce1 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 13 Apr 2023 17:02:29 -0400 Subject: [PATCH] Add annotations for evidence on package locations (#1723) * add location annotations + deb evidence annotations Signed-off-by: Alex Goodman * rename LocationData struct and Annotation helper function Signed-off-by: Alex Goodman * add failing integration test for evidence coverage Signed-off-by: Alex Goodman * add evidence to aplm cataloger locations Signed-off-by: Alex Goodman * change location annotation helper to return a location copy Signed-off-by: Alex Goodman * add evidence to binary cataloger locations Signed-off-by: Alex Goodman * updated remaining catalogers with location annotations Signed-off-by: Alex Goodman * fix unit tests Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * bump json schema Signed-off-by: Alex Goodman * partial addressing of review comments Signed-off-by: Alex Goodman * rename location.WithAnnotation Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- internal/constants.go | 2 +- schema/json/schema-7.1.2.json | 1668 +++++++++++++++++ .../common/cyclonedxhelpers/component_test.go | 2 +- .../common/spdxhelpers/to_syft_model.go | 6 +- syft/formats/github/encoder_test.go | 30 +- syft/formats/syftjson/encoder_test.go | 16 +- syft/formats/syftjson/model/package.go | 20 +- .../snapshot/TestDirectoryEncoder.golden | 4 +- .../TestEncodeFullJSONDocument.golden | 4 +- .../snapshot/TestImageEncoder.golden | 4 +- syft/formats/syftjson/to_format_model.go | 8 +- syft/formats/syftjson/to_syft_model.go | 7 +- syft/pkg/catalog_test.go | 32 +- syft/pkg/cataloger/alpm/parse_alpm_db.go | 6 +- syft/pkg/cataloger/apkdb/parse_apk_db.go | 2 +- syft/pkg/cataloger/binary/cataloger_test.go | 8 +- syft/pkg/cataloger/binary/classifier.go | 73 +- syft/pkg/cataloger/binary/package.go | 63 + syft/pkg/cataloger/cpp/parse_conanfile.go | 5 +- syft/pkg/cataloger/cpp/parse_conanlock.go | 5 +- syft/pkg/cataloger/dart/parse_pubspec_lock.go | 8 +- syft/pkg/cataloger/deb/cataloger.go | 2 +- syft/pkg/cataloger/deb/package.go | 44 +- .../pkg/cataloger/dotnet/parse_dotnet_deps.go | 6 +- syft/pkg/cataloger/elixir/parse_mix_lock.go | 17 +- .../cataloger/elixir/parse_mix_lock_test.go | 17 + syft/pkg/cataloger/erlang/parse_rebar_lock.go | 11 +- .../cataloger/erlang/parse_rebar_lock_test.go | 5 + syft/pkg/cataloger/golang/parse_go_binary.go | 20 +- .../cataloger/golang/parse_go_binary_test.go | 60 +- syft/pkg/cataloger/golang/parse_go_mod.go | 4 +- .../cataloger/haskell/parse_cabal_freeze.go | 10 +- .../pkg/cataloger/haskell/parse_stack_lock.go | 16 +- .../pkg/cataloger/haskell/parse_stack_yaml.go | 14 +- syft/pkg/cataloger/java/archive_parser.go | 20 +- .../cataloger/java/parse_gradle_lockfile.go | 8 +- syft/pkg/cataloger/java/parse_pom_xml.go | 6 +- syft/pkg/cataloger/javascript/package.go | 8 +- .../javascript/parse_package_json.go | 5 +- .../javascript/parse_package_lock.go | 5 +- syft/pkg/cataloger/nix/cataloger.go | 2 +- syft/pkg/cataloger/php/parse_composer_lock.go | 8 +- .../pkg/cataloger/php/parse_installed_json.go | 7 +- .../portage/parse_portage_contents.go | 14 +- .../pkg/cataloger/python/parse_poetry_lock.go | 9 +- .../cataloger/python/parse_requirements.go | 9 +- syft/pkg/cataloger/python/parse_setup.go | 9 +- syft/pkg/cataloger/python/parse_wheel_egg.go | 12 +- syft/pkg/cataloger/rpm/package.go | 4 +- syft/pkg/cataloger/ruby/parse_gemfile_lock.go | 2 +- syft/pkg/cataloger/ruby/parse_gemspec.go | 5 +- syft/pkg/cataloger/rust/package.go | 2 +- syft/pkg/cataloger/rust/parse_cargo_lock.go | 8 +- syft/pkg/cataloger/sbom/cataloger.go | 4 +- .../pkg/cataloger/swift/parse_podfile_lock.go | 10 +- syft/pkg/evidence.go | 7 + syft/pkg/package_test.go | 42 +- syft/source/directory_resolver_test.go | 175 +- syft/source/excluding_file_resolver_test.go | 34 +- syft/source/image_all_layers_resolver_test.go | 231 +-- syft/source/image_squash_resolver_test.go | 204 +- syft/source/location.go | 118 +- syft/source/location_set.go | 27 +- syft/source/location_set_test.go | 86 +- syft/source/location_test.go | 8 +- syft/source/mock_resolver.go | 11 +- test/integration/catalog_packages_test.go | 61 + 67 files changed, 2511 insertions(+), 849 deletions(-) create mode 100644 schema/json/schema-7.1.2.json create mode 100644 syft/pkg/cataloger/binary/package.go create mode 100644 syft/pkg/evidence.go diff --git a/internal/constants.go b/internal/constants.go index 1fda74223..127fb3924 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "7.1.1" + JSONSchemaVersion = "7.1.2" ) diff --git a/schema/json/schema-7.1.2.json b/schema/json/schema-7.1.2.json new file mode 100644 index 000000000..0a51f5518 --- /dev/null +++ b/schema/json/schema-7.1.2.json @@ -0,0 +1,1668 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/anchore/syft/syft/formats/syftjson/model/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "AlpmMetadata": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "license": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "license", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "ApkMetadata": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "license", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "BinaryMetadata": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CargoPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoapodsMetadata": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "ConanLockMetadata": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "type": "string" + }, + "build_requires": { + "type": "string" + }, + "py_requires": { + "type": "string" + }, + "options": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ConanMetadata": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "secrets": { + "items": { + "$ref": "#/$defs/Secrets" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "DpkgMetadata": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType" + ] + }, + "GemMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "GolangBinMetadata": { + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GolangModMetadata": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "JavaMetadata": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/PomProperties" + }, + "pomProject": { + "$ref": "#/$defs/PomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "KbPackageMetadata": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "MixLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "NixStoreMetadata": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "NpmPackageJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "licenses", + "homepage", + "description", + "url", + "private" + ] + }, + "NpmPackageLockJSONMetadata": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmMetadata" + }, + { + "$ref": "#/$defs/ApkMetadata" + }, + { + "$ref": "#/$defs/BinaryMetadata" + }, + { + "$ref": "#/$defs/CargoPackageMetadata" + }, + { + "$ref": "#/$defs/CocoapodsMetadata" + }, + { + "$ref": "#/$defs/ConanLockMetadata" + }, + { + "$ref": "#/$defs/ConanMetadata" + }, + { + "$ref": "#/$defs/DartPubMetadata" + }, + { + "$ref": "#/$defs/DotnetDepsMetadata" + }, + { + "$ref": "#/$defs/DpkgMetadata" + }, + { + "$ref": "#/$defs/GemMetadata" + }, + { + "$ref": "#/$defs/GolangBinMetadata" + }, + { + "$ref": "#/$defs/GolangModMetadata" + }, + { + "$ref": "#/$defs/HackageMetadata" + }, + { + "$ref": "#/$defs/JavaMetadata" + }, + { + "$ref": "#/$defs/KbPackageMetadata" + }, + { + "$ref": "#/$defs/MixLockMetadata" + }, + { + "$ref": "#/$defs/NixStoreMetadata" + }, + { + "$ref": "#/$defs/NpmPackageJSONMetadata" + }, + { + "$ref": "#/$defs/NpmPackageLockJSONMetadata" + }, + { + "$ref": "#/$defs/PhpComposerJSONMetadata" + }, + { + "$ref": "#/$defs/PortageMetadata" + }, + { + "$ref": "#/$defs/PythonPackageMetadata" + }, + { + "$ref": "#/$defs/PythonPipfileLockMetadata" + }, + { + "$ref": "#/$defs/RebarLockMetadata" + }, + { + "$ref": "#/$defs/RpmMetadata" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "PomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/PomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "PomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PortageMetadata": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipfileLockMetadata": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "RebarLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "license": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmdbFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "license", + "vendor", + "modularityLabel", + "files" + ] + }, + "RpmdbFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SearchResult": { + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ] + }, + "Secrets": { + "properties": { + "location": { + "$ref": "#/$defs/Coordinates" + }, + "secrets": { + "items": { + "$ref": "#/$defs/SearchResult" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "location", + "secrets" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "target": true + }, + "type": "object", + "required": [ + "id", + "type", + "target" + ] + } + } +} diff --git a/syft/formats/common/cyclonedxhelpers/component_test.go b/syft/formats/common/cyclonedxhelpers/component_test.go index 01bbaf97a..248d8ca46 100644 --- a/syft/formats/common/cyclonedxhelpers/component_test.go +++ b/syft/formats/common/cyclonedxhelpers/component_test.go @@ -29,7 +29,7 @@ func Test_encodeComponentProperties(t *testing.T) { input: pkg.Package{ FoundBy: "cataloger", Locations: source.NewLocationSet( - source.Location{Coordinates: source.Coordinates{RealPath: "test"}}, + source.NewLocationFromCoordinates(source.Coordinates{RealPath: "test"}), ), Metadata: pkg.ApkMetadata{ Package: "libc-utils", diff --git a/syft/formats/common/spdxhelpers/to_syft_model.go b/syft/formats/common/spdxhelpers/to_syft_model.go index 4993eea7e..e66d014a2 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model.go +++ b/syft/formats/common/spdxhelpers/to_syft_model.go @@ -220,10 +220,8 @@ func toSyftCoordinates(f *spdx.File) source.Coordinates { } func toSyftLocation(f *spdx.File) *source.Location { - return &source.Location{ - Coordinates: toSyftCoordinates(f), - VirtualPath: f.FileName, - } + l := source.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName) + return &l } func requireAndTrimPrefix(val interface{}, prefix string) string { diff --git a/syft/formats/github/encoder_test.go b/syft/formats/github/encoder_test.go index 3eb58c755..86c50132a 100644 --- a/syft/formats/github/encoder_test.go +++ b/syft/formats/github/encoder_test.go @@ -36,36 +36,30 @@ func Test_toGithubModel(t *testing.T) { Name: "pkg-1", Version: "1.0.1", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ - RealPath: "/usr/lib", - FileSystemID: "fsid-1", - }, - }, + source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/usr/lib", + FileSystemID: "fsid-1", + }), ), }, { Name: "pkg-2", Version: "2.0.2", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ - RealPath: "/usr/lib", - FileSystemID: "fsid-1", - }, - }, + source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/usr/lib", + FileSystemID: "fsid-1", + }), ), }, { Name: "pkg-3", Version: "3.0.3", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ - RealPath: "/etc", - FileSystemID: "fsid-1", - }, - }, + source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/etc", + FileSystemID: "fsid-1", + }), ), }, } { diff --git a/syft/formats/syftjson/encoder_test.go b/syft/formats/syftjson/encoder_test.go index 185658f7b..39510fcb8 100644 --- a/syft/formats/syftjson/encoder_test.go +++ b/syft/formats/syftjson/encoder_test.go @@ -53,11 +53,9 @@ func TestEncodeFullJSONDocument(t *testing.T) { Name: "package-1", Version: "1.0.1", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ - RealPath: "/a/place/a", - }, - }, + source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/a/place/a", + }), ), Type: pkg.PythonPkg, FoundBy: "the-cataloger-1", @@ -79,11 +77,9 @@ func TestEncodeFullJSONDocument(t *testing.T) { Name: "package-2", Version: "2.0.1", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ - RealPath: "/b/place/b", - }, - }, + source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/b/place/b", + }), ), Type: pkg.DebPkg, FoundBy: "the-cataloger-2", diff --git a/syft/formats/syftjson/model/package.go b/syft/formats/syftjson/model/package.go index 4567a139a..608b2be3c 100644 --- a/syft/formats/syftjson/model/package.go +++ b/syft/formats/syftjson/model/package.go @@ -21,16 +21,16 @@ type Package struct { // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package. type PackageBasicData struct { - ID string `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Type pkg.Type `json:"type"` - FoundBy string `json:"foundBy"` - Locations []source.Coordinates `json:"locations"` - Licenses []string `json:"licenses"` - Language pkg.Language `json:"language"` - CPEs []string `json:"cpes"` - PURL string `json:"purl"` + ID string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Type pkg.Type `json:"type"` + FoundBy string `json:"foundBy"` + Locations []source.Location `json:"locations"` + Licenses []string `json:"licenses"` + Language pkg.Language `json:"language"` + CPEs []string `json:"cpes"` + PURL string `json:"purl"` } // PackageCustomData contains ambiguous values (type-wise) from pkg.Package. diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index 8d30145dd..f0ad7b3ae 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden @@ -89,7 +89,7 @@ } }, "schema": { - "version": "7.1.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json" + "version": "7.1.2", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 7b4ccd02b..cccc7ae4e 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -185,7 +185,7 @@ } }, "schema": { - "version": "7.1.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json" + "version": "7.1.2", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index 7e922e6ac..a5b6a2d83 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -112,7 +112,7 @@ } }, "schema": { - "version": "7.1.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json" + "version": "7.1.2", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" } } diff --git a/syft/formats/syftjson/to_format_model.go b/syft/formats/syftjson/to_format_model.go index b5067d6ae..61ebe1007 100644 --- a/syft/formats/syftjson/to_format_model.go +++ b/syft/formats/syftjson/to_format_model.go @@ -195,12 +195,6 @@ func toPackageModel(p pkg.Package) model.Package { licenses = p.Licenses } - locations := p.Locations.ToSlice() - var coordinates = make([]source.Coordinates, len(locations)) - for i, l := range locations { - coordinates[i] = l.Coordinates - } - return model.Package{ PackageBasicData: model.PackageBasicData{ ID: string(p.ID()), @@ -208,7 +202,7 @@ func toPackageModel(p pkg.Package) model.Package { Version: p.Version, Type: p.Type, FoundBy: p.FoundBy, - Locations: coordinates, + Locations: p.Locations.ToSlice(), Licenses: licenses, Language: p.Language, CPEs: cpes, diff --git a/syft/formats/syftjson/to_syft_model.go b/syft/formats/syftjson/to_syft_model.go index 0418a41c1..cbad53fb0 100644 --- a/syft/formats/syftjson/to_syft_model.go +++ b/syft/formats/syftjson/to_syft_model.go @@ -202,16 +202,11 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package { cpes = append(cpes, value) } - var locations = make([]source.Location, len(p.Locations)) - for i, c := range p.Locations { - locations[i] = source.NewLocationFromCoordinates(c) - } - out := pkg.Package{ Name: p.Name, Version: p.Version, FoundBy: p.FoundBy, - Locations: source.NewLocationSet(locations...), + Locations: source.NewLocationSet(p.Locations...), Licenses: p.Licenses, Language: p.Language, Type: p.Type, diff --git a/syft/pkg/catalog_test.go b/syft/pkg/catalog_test.go index 625132039..371a093c1 100644 --- a/syft/pkg/catalog_test.go +++ b/syft/pkg/catalog_test.go @@ -327,45 +327,45 @@ func TestCatalog_MergeRecords(t *testing.T) { { CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")}, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "/b/path", FileSystemID: "a", }, - VirtualPath: "/another/path", - }, + "/another/path", + ), ), Type: RpmPkg, }, { CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")}, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "/b/path", FileSystemID: "b", }, - VirtualPath: "/another/path", - }, + "/another/path", + ), ), Type: RpmPkg, }, }, expectedLocations: []source.Location{ - { - Coordinates: source.Coordinates{ + source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "/b/path", FileSystemID: "a", }, - VirtualPath: "/another/path", - }, - { - Coordinates: source.Coordinates{ + "/another/path", + ), + source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "/b/path", FileSystemID: "b", }, - VirtualPath: "/another/path", - }, + "/another/path", + ), }, expectedCPECount: 2, }, diff --git a/syft/pkg/cataloger/alpm/parse_alpm_db.go b/syft/pkg/cataloger/alpm/parse_alpm_db.go index 9de9620b4..7c57ace45 100644 --- a/syft/pkg/cataloger/alpm/parse_alpm_db.go +++ b/syft/pkg/cataloger/alpm/parse_alpm_db.go @@ -70,7 +70,11 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader } return []pkg.Package{ - newPackage(*metadata, env.LinuxRelease, reader.Location), + newPackage( + *metadata, + env.LinuxRelease, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), }, nil, nil } diff --git a/syft/pkg/cataloger/apkdb/parse_apk_db.go b/syft/pkg/cataloger/apkdb/parse_apk_db.go index 1587a9eaa..3409524f4 100644 --- a/syft/pkg/cataloger/apkdb/parse_apk_db.go +++ b/syft/pkg/cataloger/apkdb/parse_apk_db.go @@ -123,7 +123,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s pkgs := make([]pkg.Package, 0, len(apks)) for _, apk := range apks { - pkgs = append(pkgs, newPackage(apk, r, reader.Location)) + pkgs = append(pkgs, newPackage(apk, r, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))) } return pkgs, discoverPackageDependencies(pkgs), nil diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index a35d3cced..af31d4fa4 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -829,12 +829,12 @@ func match(classifier string, paths ...string) pkg.ClassifierMatch { } return pkg.ClassifierMatch{ Classifier: classifier, - Location: source.Location{ - Coordinates: source.Coordinates{ + Location: source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: realPath, }, - VirtualPath: virtualPath, - }, + virtualPath, + ), } } diff --git a/syft/pkg/cataloger/binary/classifier.go b/syft/pkg/cataloger/binary/classifier.go index 458b28577..6ab18985c 100644 --- a/syft/pkg/cataloger/binary/classifier.go +++ b/syft/pkg/cataloger/binary/classifier.go @@ -7,7 +7,6 @@ import ( "debug/pe" "fmt" "io" - "reflect" "regexp" "strings" "text/template" @@ -107,7 +106,13 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri } matchMetadata := internal.MatchNamedCaptureGroups(tmplPattern, string(contents)) - return singlePackage(classifier, location, matchMetadata), nil + + p := newPackage(classifier, location, matchMetadata) + if p == nil { + return nil, nil + } + + return []pkg.Package{*p}, nil } } @@ -120,7 +125,13 @@ func fileContentsVersionMatcher(pattern string) evidenceMatcher { } matchMetadata := internal.MatchNamedCaptureGroups(pat, string(contents)) - return singlePackage(classifier, location, matchMetadata), nil + + p := newPackage(classifier, location, matchMetadata) + if p == nil { + return nil, nil + } + + return []pkg.Package{*p}, nil } } @@ -141,8 +152,8 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evide if err != nil { return nil, err } - for _, libraryLication := range locations { - pkgs, err := sharedLibraryMatcher(resolver, classifier, libraryLication) + for _, libraryLocation := range locations { + pkgs, err := sharedLibraryMatcher(resolver, classifier, libraryLocation) if err != nil { return nil, err } @@ -176,58 +187,6 @@ func mustPURL(purl string) packageurl.PackageURL { return p } -func singlePackage(classifier classifier, location source.Location, matchMetadata map[string]string) []pkg.Package { - version, ok := matchMetadata["version"] - if !ok { - return nil - } - - update := matchMetadata["update"] - - var cpes []cpe.CPE - for _, c := range classifier.CPEs { - c.Version = version - c.Update = update - cpes = append(cpes, c) - } - - p := pkg.Package{ - Name: classifier.Package, - Version: version, - Locations: source.NewLocationSet(location), - Type: pkg.BinaryPkg, - CPEs: cpes, - FoundBy: catalogerName, - MetadataType: pkg.BinaryMetadataType, - Metadata: pkg.BinaryMetadata{ - Matches: []pkg.ClassifierMatch{ - { - Classifier: classifier.Class, - Location: location, - }, - }, - }, - } - - if classifier.Type != "" { - p.Type = classifier.Type - } - - if !reflect.DeepEqual(classifier.PURL, emptyPURL) { - purl := classifier.PURL - purl.Version = version - p.PURL = purl.ToString() - } - - if classifier.Language != "" { - p.Language = classifier.Language - } - - p.SetID() - - return []pkg.Package{p} -} - func getContents(resolver source.FileResolver, location source.Location) ([]byte, error) { reader, err := resolver.FileContentsByLocation(location) if err != nil { diff --git a/syft/pkg/cataloger/binary/package.go b/syft/pkg/cataloger/binary/package.go new file mode 100644 index 000000000..7c1fb7abc --- /dev/null +++ b/syft/pkg/cataloger/binary/package.go @@ -0,0 +1,63 @@ +package binary + +import ( + "reflect" + + "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/source" +) + +func newPackage(classifier classifier, location source.Location, matchMetadata map[string]string) *pkg.Package { + version, ok := matchMetadata["version"] + if !ok { + return nil + } + + update := matchMetadata["update"] + + var cpes []cpe.CPE + for _, c := range classifier.CPEs { + c.Version = version + c.Update = update + cpes = append(cpes, c) + } + + p := pkg.Package{ + Name: classifier.Package, + Version: version, + Locations: source.NewLocationSet( + location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + Type: pkg.BinaryPkg, + CPEs: cpes, + FoundBy: catalogerName, + MetadataType: pkg.BinaryMetadataType, + Metadata: pkg.BinaryMetadata{ + Matches: []pkg.ClassifierMatch{ + { + Classifier: classifier.Class, + Location: location, + }, + }, + }, + } + + if classifier.Type != "" { + p.Type = classifier.Type + } + + if !reflect.DeepEqual(classifier.PURL, emptyPURL) { + purl := classifier.PURL + purl.Version = version + p.PURL = purl.ToString() + } + + if classifier.Language != "" { + p.Language = classifier.Language + } + + p.SetID() + + return &p +} diff --git a/syft/pkg/cataloger/cpp/parse_conanfile.go b/syft/pkg/cataloger/cpp/parse_conanfile.go index eaa2d1b5b..fdaf08026 100644 --- a/syft/pkg/cataloger/cpp/parse_conanfile.go +++ b/syft/pkg/cataloger/cpp/parse_conanfile.go @@ -48,7 +48,10 @@ func parseConanfile(_ source.FileResolver, _ *generic.Environment, reader source continue } - p := newConanfilePackage(m, reader.Location) + p := newConanfilePackage( + m, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if p == nil { continue } diff --git a/syft/pkg/cataloger/cpp/parse_conanlock.go b/syft/pkg/cataloger/cpp/parse_conanlock.go index e7310c22f..b3bcf31d5 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock.go @@ -44,7 +44,10 @@ func parseConanlock(_ source.FileResolver, _ *generic.Environment, reader source Context: node.Context, } - p := newConanlockPackage(metadata, reader.Location) + p := newConanlockPackage( + metadata, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if p != nil { pkgs = append(pkgs, *p) diff --git a/syft/pkg/cataloger/dart/parse_pubspec_lock.go b/syft/pkg/cataloger/dart/parse_pubspec_lock.go index 0f12b0a41..bde8caf66 100644 --- a/syft/pkg/cataloger/dart/parse_pubspec_lock.go +++ b/syft/pkg/cataloger/dart/parse_pubspec_lock.go @@ -58,7 +58,13 @@ func parsePubspecLock(_ source.FileResolver, _ *generic.Environment, reader sour for _, name := range names { pubPkg := p.Packages[name] - pkgs = append(pkgs, newPubspecLockPackage(name, pubPkg, reader.Location)) + pkgs = append(pkgs, + newPubspecLockPackage( + name, + pubPkg, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/deb/cataloger.go b/syft/pkg/cataloger/deb/cataloger.go index fb25550c4..70bd8e426 100644 --- a/syft/pkg/cataloger/deb/cataloger.go +++ b/syft/pkg/cataloger/deb/cataloger.go @@ -1,5 +1,5 @@ /* -Package dpkg provides a concrete Cataloger implementation for Debian package DB status files. +Package deb provides a concrete Cataloger implementation for Debian package DB status files. */ package deb diff --git a/syft/pkg/cataloger/deb/package.go b/syft/pkg/cataloger/deb/package.go index 3f10dbe3f..2113c8472 100644 --- a/syft/pkg/cataloger/deb/package.go +++ b/syft/pkg/cataloger/deb/package.go @@ -25,7 +25,7 @@ func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver sou p := pkg.Package{ Name: d.Package, Version: d.Version, - Locations: source.NewLocationSet(dbLocation), + Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(d, release), Type: pkg.DebPkg, MetadataType: pkg.DpkgMetadataType, @@ -162,6 +162,7 @@ func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Lo return files, locations } +//nolint:dupl func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { var md5Reader io.ReadCloser var err error @@ -182,17 +183,22 @@ func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", m.Package+md5sumsExt)) } - // this is unexpected, but not a show-stopper - if location != nil { - md5Reader, err = resolver.FileContentsByLocation(*location) - if err != nil { - log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err) - } + if location == nil { + return nil, nil } - return md5Reader, location + // this is unexpected, but not a show-stopper + md5Reader, err = resolver.FileContentsByLocation(*location) + if err != nil { + log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err) + } + + l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation) + + return md5Reader, &l } +//nolint:dupl func fetchConffileContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { var reader io.ReadCloser var err error @@ -213,15 +219,19 @@ func fetchConffileContents(resolver source.FileResolver, dbLocation source.Locat location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", m.Package+conffilesExt)) } - // this is unexpected, but not a show-stopper - if location != nil { - reader, err = resolver.FileContentsByLocation(*location) - if err != nil { - log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err) - } + if location == nil { + return nil, nil } - return reader, location + // this is unexpected, but not a show-stopper + reader, err = resolver.FileContentsByLocation(*location) + if err != nil { + log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err) + } + + l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation) + + return reader, &l } func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { @@ -243,7 +253,9 @@ func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Loca log.Warnf("failed to fetch deb copyright contents (package=%s): %w", m.Package, err) } - return reader, location + l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation) + + return reader, &l } func md5Key(metadata pkg.DpkgMetadata) string { diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go index 49a23067c..0e322d3db 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go +++ b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go @@ -45,7 +45,11 @@ func parseDotnetDeps(_ source.FileResolver, _ *generic.Environment, reader sourc for _, nameVersion := range names { lib := p.Libraries[nameVersion] - dotnetPkg := newDotnetDepsPackage(nameVersion, lib, reader.Location) + dotnetPkg := newDotnetDepsPackage( + nameVersion, + lib, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if dotnetPkg != nil { pkgs = append(pkgs, *dotnetPkg) diff --git a/syft/pkg/cataloger/elixir/parse_mix_lock.go b/syft/pkg/cataloger/elixir/parse_mix_lock.go index 075e5fa5e..6de1fc8f7 100644 --- a/syft/pkg/cataloger/elixir/parse_mix_lock.go +++ b/syft/pkg/cataloger/elixir/parse_mix_lock.go @@ -43,11 +43,16 @@ func parseMixLock(_ source.FileResolver, _ *generic.Environment, reader source.L continue } - packages = append(packages, newPackage(pkg.MixLockMetadata{ - Name: name, - Version: version, - PkgHash: hash, - PkgHashExt: hashExt, - })) + packages = append(packages, + newPackage( + pkg.MixLockMetadata{ + Name: name, + Version: version, + PkgHash: hash, + PkgHashExt: hashExt, + }, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } } diff --git a/syft/pkg/cataloger/elixir/parse_mix_lock_test.go b/syft/pkg/cataloger/elixir/parse_mix_lock_test.go index 11de894ac..2f5de43d4 100644 --- a/syft/pkg/cataloger/elixir/parse_mix_lock_test.go +++ b/syft/pkg/cataloger/elixir/parse_mix_lock_test.go @@ -6,15 +6,18 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" ) func TestParseMixLock(t *testing.T) { + locations := source.NewLocationSet(source.NewLocation("test-fixtures/mix.lock")) expected := []pkg.Package{ { Name: "castore", Version: "0.1.17", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/castore@0.1.17", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -29,6 +32,7 @@ func TestParseMixLock(t *testing.T) { Version: "1.1.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/connection@1.1.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -43,6 +47,7 @@ func TestParseMixLock(t *testing.T) { Version: "2.9.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/cowboy@2.9.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -57,6 +62,7 @@ func TestParseMixLock(t *testing.T) { Version: "0.4.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/cowboy_telemetry@0.4.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -71,6 +77,7 @@ func TestParseMixLock(t *testing.T) { Version: "2.11.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/cowlib@2.11.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -85,6 +92,7 @@ func TestParseMixLock(t *testing.T) { Version: "2.4.2", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/db_connection@2.4.2", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -99,6 +107,7 @@ func TestParseMixLock(t *testing.T) { Version: "2.0.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/decimal@2.0.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -113,6 +122,7 @@ func TestParseMixLock(t *testing.T) { Version: "1.4.25", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/earmark_parser@1.4.25", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -127,6 +137,7 @@ func TestParseMixLock(t *testing.T) { Version: "3.8.1", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/ecto@3.8.1", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -141,6 +152,7 @@ func TestParseMixLock(t *testing.T) { Version: "3.8.1", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/ecto_sql@3.8.1", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -155,6 +167,7 @@ func TestParseMixLock(t *testing.T) { Version: "0.5.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/esbuild@0.5.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -169,6 +182,7 @@ func TestParseMixLock(t *testing.T) { Version: "0.28.4", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/ex_doc@0.28.4", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -183,6 +197,7 @@ func TestParseMixLock(t *testing.T) { Version: "0.19.1", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/gettext@0.19.1", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -197,6 +212,7 @@ func TestParseMixLock(t *testing.T) { Version: "0.1.1", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/hpax@0.1.1", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ @@ -211,6 +227,7 @@ func TestParseMixLock(t *testing.T) { Version: "1.3.0", Language: pkg.Elixir, Type: pkg.HexPkg, + Locations: locations, PURL: "pkg:hex/jason@1.3.0", MetadataType: pkg.MixLockMetadataType, Metadata: pkg.MixLockMetadata{ diff --git a/syft/pkg/cataloger/erlang/parse_rebar_lock.go b/syft/pkg/cataloger/erlang/parse_rebar_lock.go index 8ff1dc5f9..547a4d3ec 100644 --- a/syft/pkg/cataloger/erlang/parse_rebar_lock.go +++ b/syft/pkg/cataloger/erlang/parse_rebar_lock.go @@ -48,10 +48,13 @@ func parseRebarLock(_ source.FileResolver, _ *generic.Environment, reader source version = versionNode.Get(2).Get(1).String() } - p := newPackage(pkg.RebarLockMetadata{ - Name: name, - Version: version, - }) + p := newPackage( + pkg.RebarLockMetadata{ + Name: name, + Version: version, + }, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) pkgMap[name] = &p } diff --git a/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go b/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go index 7986f5b5f..b12931432 100644 --- a/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go +++ b/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go @@ -6,6 +6,7 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" ) func TestParseRebarLock(t *testing.T) { @@ -261,6 +262,10 @@ func TestParseRebarLock(t *testing.T) { // TODO: relationships are not under test var expectedRelationships []artifact.Relationship + for idx := range test.expected { + test.expected[idx].Locations = source.NewLocationSet(source.NewLocation(test.fixture)) + } + pkgtest.TestFileParser(t, test.fixture, parseRebarLock, test.expected, expectedRelationships) }) } diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index 81e8c3d56..c7b99fd25 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -62,7 +62,15 @@ func (c *goBinaryCataloger) parseGoBinary(resolver source.FileResolver, _ *gener func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod *debug.BuildInfo, arch string, location source.Location) pkg.Package { gbs := getBuildSettings(mod.Settings) - main := c.newGoBinaryPackage(resolver, &mod.Main, mod.Main.Path, mod.GoVersion, arch, gbs, location) + main := c.newGoBinaryPackage( + resolver, + &mod.Main, + mod.Main.Path, + mod.GoVersion, + arch, + gbs, + location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if main.Version == devel { if version, ok := gbs["vcs.revision"]; ok { if timestamp, ok := gbs["vcs.time"]; ok { @@ -204,7 +212,15 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver source.FileResolver, locatio if dep == nil { continue } - p := c.newGoBinaryPackage(resolver, dep, mod.Main.Path, mod.GoVersion, arch, nil, location) + p := c.newGoBinaryPackage( + resolver, + dep, + mod.Main.Path, + mod.GoVersion, + arch, + nil, + location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if pkg.IsValid(&p) { pkgs = append(pkgs, p) } diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 93b91ff22..c9d78a2ea 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -136,12 +136,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Version: "(devel)", PURL: "pkg:golang/github.com/anchore/syft@(devel)", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -183,12 +183,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{}, @@ -226,12 +226,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -262,12 +262,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -320,12 +320,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Version: "v0.0.0-20221014195457-41bc6bb41035", PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035", Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -375,12 +375,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -397,12 +397,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -452,12 +452,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -473,12 +473,12 @@ func TestBuildGoPkgInfo(t *testing.T) { Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( - source.Location{ - Coordinates: source.Coordinates{ + source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ @@ -502,12 +502,12 @@ func TestBuildGoPkgInfo(t *testing.T) { } p.SetID() } - location := source.Location{ - Coordinates: source.Coordinates{ + location := source.NewLocationFromCoordinates( + source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, - } + ) c := goBinaryCataloger{} pkgs := c.buildGoPkgInfo(source.NewMockResolverForPaths(), location, test.mod, test.arch) diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index 8846cf904..203ba3189 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -51,7 +51,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic Name: m.Mod.Path, Version: m.Mod.Version, Licenses: licenses, - Locations: source.NewLocationSet(reader.Location), + Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(m.Mod.Path, m.Mod.Version), Language: pkg.Go, Type: pkg.GoModulePkg, @@ -73,7 +73,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic Name: m.New.Path, Version: m.New.Version, Licenses: licenses, - Locations: source.NewLocationSet(reader.Location), + Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(m.New.Path, m.New.Version), Language: pkg.Go, Type: pkg.GoModulePkg, diff --git a/syft/pkg/cataloger/haskell/parse_cabal_freeze.go b/syft/pkg/cataloger/haskell/parse_cabal_freeze.go index 80631b61a..d95446984 100644 --- a/syft/pkg/cataloger/haskell/parse_cabal_freeze.go +++ b/syft/pkg/cataloger/haskell/parse_cabal_freeze.go @@ -46,6 +46,14 @@ func parseCabalFreeze(_ source.FileResolver, _ *generic.Environment, reader sour fields := strings.Split(line, " ==") pkgName, pkgVersion := fields[0], fields[1] - pkgs = append(pkgs, newPackage(pkgName, pkgVersion, nil, reader.Location)) + pkgs = append( + pkgs, + newPackage( + pkgName, + pkgVersion, + nil, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } } diff --git a/syft/pkg/cataloger/haskell/parse_stack_lock.go b/syft/pkg/cataloger/haskell/parse_stack_lock.go index 58ce52c30..de41a5767 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_lock.go +++ b/syft/pkg/cataloger/haskell/parse_stack_lock.go @@ -62,10 +62,18 @@ func parseStackLock(_ source.FileResolver, _ *generic.Environment, reader source for _, pack := range lockFile.Packages { pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(pack.Completed.Hackage) - pkgs = append(pkgs, newPackage(pkgName, pkgVersion, &pkg.HackageMetadata{ - PkgHash: pkgHash, - SnapshotURL: snapshotURL, - }, reader.Location)) + pkgs = append( + pkgs, + newPackage( + pkgName, + pkgVersion, + &pkg.HackageMetadata{ + PkgHash: pkgHash, + SnapshotURL: snapshotURL, + }, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/haskell/parse_stack_yaml.go b/syft/pkg/cataloger/haskell/parse_stack_yaml.go index 7325c8b79..8404f4bf4 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_yaml.go +++ b/syft/pkg/cataloger/haskell/parse_stack_yaml.go @@ -34,9 +34,17 @@ func parseStackYaml(_ source.FileResolver, _ *generic.Environment, reader source var pkgs []pkg.Package for _, dep := range stackFile.ExtraDeps { pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(dep) - pkgs = append(pkgs, newPackage(pkgName, pkgVersion, &pkg.HackageMetadata{ - PkgHash: pkgHash, - }, reader.Location)) + pkgs = append( + pkgs, + newPackage( + pkgName, + pkgVersion, + &pkg.HackageMetadata{ + PkgHash: pkgHash, + }, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index a0e8b7465..0128709d5 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -186,11 +186,13 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { } return &pkg.Package{ - Name: selectName(manifest, j.fileInfo), - Version: selectVersion(manifest, j.fileInfo), - Licenses: selectLicense(manifest), - Language: pkg.Java, - Locations: source.NewLocationSet(j.location), + Name: selectName(manifest, j.fileInfo), + Version: selectVersion(manifest, j.fileInfo), + Licenses: selectLicense(manifest), + Language: pkg.Java, + Locations: source.NewLocationSet( + j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), Type: j.fileInfo.pkgType(), MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ @@ -380,9 +382,11 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po // discovered props = new package p := pkg.Package{ - Name: pomProperties.ArtifactID, - Version: pomProperties.Version, - Locations: source.NewLocationSet(location), + Name: pomProperties.ArtifactID, + Version: pomProperties.Version, + Locations: source.NewLocationSet( + location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), Language: pkg.Java, Type: pomProperties.PkgTypeIndicated(), MetadataType: pkg.JavaMetadataType, diff --git a/syft/pkg/cataloger/java/parse_gradle_lockfile.go b/syft/pkg/cataloger/java/parse_gradle_lockfile.go index 65ea51466..803639ab4 100644 --- a/syft/pkg/cataloger/java/parse_gradle_lockfile.go +++ b/syft/pkg/cataloger/java/parse_gradle_lockfile.go @@ -49,9 +49,11 @@ func parseGradleLockfile(_ source.FileResolver, _ *generic.Environment, reader s // map the dependencies for _, dep := range dependencies { mappedPkg := pkg.Package{ - Name: dep.Name, - Version: dep.Version, - Locations: source.NewLocationSet(reader.Location), + Name: dep.Name, + Version: dep.Version, + Locations: source.NewLocationSet( + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index 0f30e0929..8df940869 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -29,7 +29,11 @@ func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.L var pkgs []pkg.Package for _, dep := range pom.Dependencies { - p := newPackageFromPom(pom, dep, reader.Location) + p := newPackageFromPom( + pom, + dep, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) if p.Name == "" { continue } diff --git a/syft/pkg/cataloger/javascript/package.go b/syft/pkg/cataloger/javascript/package.go index fb5241334..314ce64e5 100644 --- a/syft/pkg/cataloger/javascript/package.go +++ b/syft/pkg/cataloger/javascript/package.go @@ -66,7 +66,7 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location), + Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -89,7 +89,7 @@ func newPackageLockV2Package(resolver source.FileResolver, location source.Locat pkg.Package{ Name: name, Version: u.Version, - Locations: source.NewLocationSet(location), + Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, u.Version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -107,7 +107,7 @@ func newPnpmPackage(resolver source.FileResolver, location source.Location, name pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location), + Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -122,7 +122,7 @@ func newYarnLockPackage(resolver source.FileResolver, location source.Location, pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location), + Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, diff --git a/syft/pkg/cataloger/javascript/parse_package_json.go b/syft/pkg/cataloger/javascript/parse_package_json.go index edf403c7a..2d943e710 100644 --- a/syft/pkg/cataloger/javascript/parse_package_json.go +++ b/syft/pkg/cataloger/javascript/parse_package_json.go @@ -68,7 +68,10 @@ func parsePackageJSON(_ source.FileResolver, _ *generic.Environment, reader sour return nil, nil, nil } - pkgs = append(pkgs, newPackageJSONPackage(p, reader.Location)) + pkgs = append( + pkgs, + newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + ) } pkg.Sort(pkgs) diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 85574153f..d0a75f1ac 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -113,7 +113,10 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read name = pkgMeta.Name } - pkgs = append(pkgs, newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta)) + pkgs = append( + pkgs, + newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta), + ) } } diff --git a/syft/pkg/cataloger/nix/cataloger.go b/syft/pkg/cataloger/nix/cataloger.go index 4343844b1..b4b440c26 100644 --- a/syft/pkg/cataloger/nix/cataloger.go +++ b/syft/pkg/cataloger/nix/cataloger.go @@ -56,7 +56,7 @@ func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, [ continue } - p := newNixStorePackage(*storePath, location) + p := newNixStorePackage(*storePath, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) pkgs = append(pkgs, p) } diff --git a/syft/pkg/cataloger/php/parse_composer_lock.go b/syft/pkg/cataloger/php/parse_composer_lock.go index e8d258c72..516146350 100644 --- a/syft/pkg/cataloger/php/parse_composer_lock.go +++ b/syft/pkg/cataloger/php/parse_composer_lock.go @@ -32,7 +32,13 @@ func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader sou return nil, nil, fmt.Errorf("failed to parse composer.lock file: %w", err) } for _, m := range lock.Packages { - pkgs = append(pkgs, newComposerLockPackage(m, reader.Location)) + pkgs = append( + pkgs, + newComposerLockPackage( + m, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } } diff --git a/syft/pkg/cataloger/php/parse_installed_json.go b/syft/pkg/cataloger/php/parse_installed_json.go index b5cd1b86a..e5309c5f3 100644 --- a/syft/pkg/cataloger/php/parse_installed_json.go +++ b/syft/pkg/cataloger/php/parse_installed_json.go @@ -53,7 +53,12 @@ func parseInstalledJSON(_ source.FileResolver, _ *generic.Environment, reader so return nil, nil, fmt.Errorf("failed to parse installed.json file: %w", err) } for _, pkgMeta := range lock.Packages { - pkgs = append(pkgs, newComposerLockPackage(pkgMeta, reader.Location)) + pkgs = append( + pkgs, + newComposerLockPackage(pkgMeta, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } } diff --git a/syft/pkg/cataloger/portage/parse_portage_contents.go b/syft/pkg/cataloger/portage/parse_portage_contents.go index dba65ace0..799cb8b2a 100644 --- a/syft/pkg/cataloger/portage/parse_portage_contents.go +++ b/syft/pkg/cataloger/portage/parse_portage_contents.go @@ -37,10 +37,12 @@ func parsePortageContents(resolver source.FileResolver, _ *generic.Environment, } p := pkg.Package{ - Name: name, - Version: version, - PURL: packageURL(name, version), - Locations: source.NewLocationSet(), + Name: name, + Version: version, + PURL: packageURL(name, version), + Locations: source.NewLocationSet( + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), Type: pkg.PortagePkg, MetadataType: pkg.PortageMetadataType, Metadata: pkg.PortageMetadata{ @@ -117,7 +119,7 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk licenses := findings.ToSlice() sort.Strings(licenses) p.Licenses = licenses - p.Locations.Add(*location) + p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) } func addSize(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { @@ -150,5 +152,5 @@ func addSize(resolver source.FileResolver, dbLocation source.Location, p *pkg.Pa } p.Metadata = entry - p.Locations.Add(*location) + p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) } diff --git a/syft/pkg/cataloger/python/parse_poetry_lock.go b/syft/pkg/cataloger/python/parse_poetry_lock.go index 52e33e618..0e29de017 100644 --- a/syft/pkg/cataloger/python/parse_poetry_lock.go +++ b/syft/pkg/cataloger/python/parse_poetry_lock.go @@ -39,7 +39,14 @@ func parsePoetryLock(_ source.FileResolver, _ *generic.Environment, reader sourc var pkgs []pkg.Package for _, p := range metadata.Packages { - pkgs = append(pkgs, newPackageForIndex(p.Name, p.Version, reader.Location)) + pkgs = append( + pkgs, + newPackageForIndex( + p.Name, + p.Version, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/python/parse_requirements.go b/syft/pkg/cataloger/python/parse_requirements.go index 599fe3db5..9b6e5db58 100644 --- a/syft/pkg/cataloger/python/parse_requirements.go +++ b/syft/pkg/cataloger/python/parse_requirements.go @@ -61,7 +61,14 @@ func parseRequirementsTxt(_ source.FileResolver, _ *generic.Environment, reader log.WithFields("path", reader.RealPath).Debugf("found empty package in requirements.txt line: %q", line) continue } - packages = append(packages, newPackageForIndex(name, version, reader.Location)) + packages = append( + packages, + newPackageForIndex( + name, + version, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } if err := scanner.Err(); err != nil { diff --git a/syft/pkg/cataloger/python/parse_setup.go b/syft/pkg/cataloger/python/parse_setup.go index 05a2224df..ee91f6ada 100644 --- a/syft/pkg/cataloger/python/parse_setup.go +++ b/syft/pkg/cataloger/python/parse_setup.go @@ -53,7 +53,14 @@ func parseSetup(_ source.FileResolver, _ *generic.Environment, reader source.Loc continue } - packages = append(packages, newPackageForIndex(name, version, reader.Location)) + packages = append( + packages, + newPackageForIndex( + name, + version, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } } diff --git a/syft/pkg/cataloger/python/parse_wheel_egg.go b/syft/pkg/cataloger/python/parse_wheel_egg.go index 1ccf4664b..7299e150c 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg.go @@ -47,7 +47,7 @@ func fetchInstalledFiles(resolver source.FileResolver, metadataLocation source.L installedFilesRef := resolver.RelativeFileByPath(metadataLocation, installedFilesPath) if installedFilesRef != nil { - sources = append(sources, *installedFilesRef) + sources = append(sources, installedFilesRef.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) installedFilesContents, err := resolver.FileContentsByLocation(*installedFilesRef) if err != nil { @@ -78,7 +78,7 @@ func fetchRecordFiles(resolver source.FileResolver, metadataLocation source.Loca recordRef := resolver.RelativeFileByPath(metadataLocation, recordPath) if recordRef != nil { - sources = append(sources, *recordRef) + sources = append(sources, recordRef.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) recordContents, err := resolver.FileContentsByLocation(*recordRef) if err != nil { @@ -105,7 +105,7 @@ func fetchTopLevelPackages(resolver source.FileResolver, metadataLocation source return nil, nil, nil } - sources = append(sources, *topLevelLocation) + sources = append(sources, topLevelLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) topLevelContents, err := resolver.FileContentsByLocation(*topLevelLocation) if err != nil { @@ -134,7 +134,7 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo return nil, nil, nil } - sources = append(sources, *directURLLocation) + sources = append(sources, directURLLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) directURLContents, err := resolver.FileContentsByLocation(*directURLLocation) if err != nil { @@ -161,7 +161,9 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo // assembleEggOrWheelMetadata discovers and accumulates python package metadata from multiple file sources and returns a single metadata object as well as a list of files where the metadata was derived from. func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*pkg.PythonPackageMetadata, []source.Location, error) { - var sources = []source.Location{metadataLocation} + var sources = []source.Location{ + metadataLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + } metadataContents, err := resolver.FileContentsByLocation(metadataLocation) if err != nil { diff --git a/syft/pkg/cataloger/rpm/package.go b/syft/pkg/cataloger/rpm/package.go index cf277e780..77e5e14be 100644 --- a/syft/pkg/cataloger/rpm/package.go +++ b/syft/pkg/cataloger/rpm/package.go @@ -13,12 +13,12 @@ import ( "github.com/anchore/syft/syft/source" ) -func newPackage(dbLocation source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package { +func newPackage(location source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package { p := pkg.Package{ Name: metadata.Name, Version: toELVersion(metadata), PURL: packageURL(metadata, distro), - Locations: source.NewLocationSet(dbLocation), + Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: metadata, diff --git a/syft/pkg/cataloger/ruby/parse_gemfile_lock.go b/syft/pkg/cataloger/ruby/parse_gemfile_lock.go index e48326ef3..8667102f2 100644 --- a/syft/pkg/cataloger/ruby/parse_gemfile_lock.go +++ b/syft/pkg/cataloger/ruby/parse_gemfile_lock.go @@ -44,7 +44,7 @@ func parseGemFileLockEntries(_ source.FileResolver, _ *generic.Environment, read newGemfileLockPackage( candidate[0], strings.Trim(candidate[1], "()"), - reader.Location, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), ) } diff --git a/syft/pkg/cataloger/ruby/parse_gemspec.go b/syft/pkg/cataloger/ruby/parse_gemspec.go index 08bc9fe96..6bb11a809 100644 --- a/syft/pkg/cataloger/ruby/parse_gemspec.go +++ b/syft/pkg/cataloger/ruby/parse_gemspec.go @@ -95,7 +95,10 @@ func parseGemSpecEntries(_ source.FileResolver, _ *generic.Environment, reader s return nil, nil, fmt.Errorf("unable to decode gem metadata: %w", err) } - pkgs = append(pkgs, newGemspecPackage(metadata, reader.Location)) + pkgs = append( + pkgs, + newGemspecPackage(metadata, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/rust/package.go b/syft/pkg/cataloger/rust/package.go index 2a6b995a0..1d661bce3 100644 --- a/syft/pkg/cataloger/rust/package.go +++ b/syft/pkg/cataloger/rust/package.go @@ -31,7 +31,7 @@ func newPackagesFromAudit(location source.Location, versionInfo rustaudit.Versio for _, dep := range versionInfo.Packages { dep := dep - p := newPackageFromAudit(&dep, location) + p := newPackageFromAudit(&dep, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) if pkg.IsValid(&p) && dep.Kind == rustaudit.Runtime { pkgs = append(pkgs, p) } diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock.go b/syft/pkg/cataloger/rust/parse_cargo_lock.go index 04bf4d20d..0e9d582a7 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock.go @@ -36,7 +36,13 @@ func parseCargoLock(_ source.FileResolver, _ *generic.Environment, reader source if p.Dependencies == nil { p.Dependencies = make([]string, 0) } - pkgs = append(pkgs, newPackageFromCargoMetadata(p, reader.Location)) + pkgs = append( + pkgs, + newPackageFromCargoMetadata( + p, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/cataloger/sbom/cataloger.go b/syft/pkg/cataloger/sbom/cataloger.go index 0c82f452e..17e6618e6 100644 --- a/syft/pkg/cataloger/sbom/cataloger.go +++ b/syft/pkg/cataloger/sbom/cataloger.go @@ -47,7 +47,9 @@ func parseSBOM(_ source.FileResolver, _ *generic.Environment, reader source.Loca // Why not keep the original list of locations? Since the "locations" field is meant to capture // where there is evidence of this file, and the catalogers have not run against any file other than, // the SBOM, this is the only location that is relevant for this cataloger. - p.Locations = source.NewLocationSet(reader.Location) + p.Locations = source.NewLocationSet( + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) p.FoundBy = catalogerName pkgs = append(pkgs, p) diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock.go b/syft/pkg/cataloger/swift/parse_podfile_lock.go index 4b6409bff..afff41ae9 100644 --- a/syft/pkg/cataloger/swift/parse_podfile_lock.go +++ b/syft/pkg/cataloger/swift/parse_podfile_lock.go @@ -59,7 +59,15 @@ func parsePodfileLock(_ source.FileResolver, _ *generic.Environment, reader sour return nil, nil, fmt.Errorf("malformed podfile.lock: incomplete checksums") } - pkgs = append(pkgs, newPackage(podName, podVersion, pkgHash, reader.Location)) + pkgs = append( + pkgs, + newPackage( + podName, + podVersion, + pkgHash, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) } return pkgs, nil, nil diff --git a/syft/pkg/evidence.go b/syft/pkg/evidence.go new file mode 100644 index 000000000..9a7ae5f52 --- /dev/null +++ b/syft/pkg/evidence.go @@ -0,0 +1,7 @@ +package pkg + +const ( + EvidenceAnnotationKey = "evidence" + PrimaryEvidenceAnnotation = "primary" + SupportingEvidenceAnnotation = "supporting" +) diff --git a/syft/pkg/package_test.go b/syft/pkg/package_test.go index 51854c061..69addce28 100644 --- a/syft/pkg/package_test.go +++ b/syft/pkg/package_test.go @@ -7,19 +7,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/source" ) func TestIDUniqueness(t *testing.T) { - originalLocation := source.Location{ - Coordinates: source.Coordinates{ + originalLocation := source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "39.0742° N, 21.8243° E", FileSystemID: "Earth", }, - VirtualPath: "/Ancient-Greece", - } + "/Ancient-Greece", + ) + originalPkg := Package{ Name: "pi", Version: "3.14", @@ -238,13 +238,13 @@ func TestIDUniqueness(t *testing.T) { } func TestPackage_Merge(t *testing.T) { - originalLocation := source.Location{ - Coordinates: source.Coordinates{ + originalLocation := source.NewVirtualLocationFromCoordinates( + source.Coordinates{ RealPath: "39.0742° N, 21.8243° E", FileSystemID: "Earth", }, - VirtualPath: "/Ancient-Greece", - } + "/Ancient-Greece", + ) similarLocation := originalLocation similarLocation.FileSystemID = "Mars" @@ -423,13 +423,23 @@ func TestPackage_Merge(t *testing.T) { cmp.AllowUnexported(Package{}), cmp.Comparer( func(x, y source.LocationSet) bool { - return cmp.Equal( - x.ToSlice(), y.ToSlice(), - cmp.AllowUnexported(source.Location{}), - cmp.AllowUnexported(file.Reference{}), - ) + xs := x.ToSlice() + ys := y.ToSlice() + + if len(xs) != len(ys) { + return false + } + for i, xe := range xs { + ye := ys[i] + if !locationComparer(xe, ye) { + return false + } + } + + return true }, ), + cmp.Comparer(locationComparer), ); diff != "" { t.Errorf("unexpected result from parsing (-expected +actual)\n%s", diff) } @@ -437,6 +447,10 @@ func TestPackage_Merge(t *testing.T) { } } +func locationComparer(x, y source.Location) bool { + return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath) +} + func TestIsValid(t *testing.T) { cases := []struct { name string diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index 550488aaa..c976fc343 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -765,30 +765,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "file-1.txt", - }, - //VirtualPath: "file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-3.txt", - }, - //VirtualPath: "file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - //VirtualPath: "file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "parent/file-4.txt", - }, - //VirtualPath: "parent/file-4.txt", - }, + NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt" }, }, { @@ -801,31 +781,11 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "file-1.txt", - }, - VirtualPath: "link-1", - }, - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - VirtualPath: "link-2", - }, + NewVirtualLocation("file-1.txt", "link-1"), + NewVirtualLocation("file-2.txt", "link-2"), // we already have this real file path via another link, so only one is returned - //{ - // Coordinates: Coordinates{ - // RealPath: "file-2.txt", - // }, - // VirtualPath: "link-indirect", - //}, - { - Coordinates: Coordinates{ - RealPath: "file-3.txt", - }, - VirtualPath: "link-within", - }, + //NewVirtualLocation("file-2.txt", "link-indirect"), + NewVirtualLocation("file-3.txt", "link-within"), }, }, { @@ -838,12 +798,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // this has two copies in the base image, which overwrites the same location - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - //VirtualPath: "file-2.txt", - }, + NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt", }, }, { @@ -855,30 +810,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "file-1.txt", - }, - //VirtualPath: "file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - //VirtualPath: "file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-3.txt", - }, - //VirtualPath: "file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "parent/file-4.txt", - }, - //VirtualPath: "parent/file-4.txt", - }, + NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" }, }, { @@ -890,33 +825,41 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ { - Coordinates: Coordinates{ - RealPath: "file-1.txt", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "file-1.txt", + }, + VirtualPath: "link-1", + ref: file.Reference{RealPath: "file-1.txt"}, }, - VirtualPath: "link-1", - ref: file.Reference{RealPath: "file-1.txt"}, }, { - Coordinates: Coordinates{ - RealPath: "file-2.txt", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "file-2.txt", + }, + VirtualPath: "link-2", + ref: file.Reference{RealPath: "file-2.txt"}, }, - VirtualPath: "link-2", - ref: file.Reference{RealPath: "file-2.txt"}, }, // we already have this real file path via another link, so only one is returned //{ - // Coordinates: Coordinates{ - // RealPath: "file-2.txt", - // }, - // VirtualPath: "link-indirect", - // ref: file.Reference{RealPath: "file-2.txt"}, + // LocationData: LocationData{ + // Coordinates: Coordinates{ + // RealPath: "file-2.txt", + // }, + // VirtualPath: "link-indirect", + // ref: file.Reference{RealPath: "file-2.txt"}, + // }, //}, { - Coordinates: Coordinates{ - RealPath: "file-3.txt", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "file-3.txt", + }, + VirtualPath: "link-within", + ref: file.Reference{RealPath: "file-3.txt"}, }, - VirtualPath: "link-within", - ref: file.Reference{RealPath: "file-3.txt"}, }, }, }, @@ -929,30 +872,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "file-1.txt", - }, - //VirtualPath: "file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - //VirtualPath: "file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "file-3.txt", - }, - //VirtualPath: "file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "parent/file-4.txt", - }, - //VirtualPath: "parent/file-4.txt", - }, + NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" }, }, { @@ -965,12 +888,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - VirtualPath: "link-2", - }, + NewVirtualLocation("file-2.txt", "link-2"), }, }, { @@ -983,12 +901,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - VirtualPath: "link-indirect", - }, + NewVirtualLocation("file-2.txt", "link-indirect"), }, }, } diff --git a/syft/source/excluding_file_resolver_test.go b/syft/source/excluding_file_resolver_test.go index 968d2b6a8..c448e3921 100644 --- a/syft/source/excluding_file_resolver_test.go +++ b/syft/source/excluding_file_resolver_test.go @@ -6,8 +6,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - - "github.com/anchore/stereoscope/pkg/file" ) func TestExcludingResolver(t *testing.T) { @@ -79,25 +77,25 @@ func TestExcludingResolver(t *testing.T) { for _, path := range diff { assert.False(t, er.HasPath(path)) - c, err := er.FileContentsByLocation(makeLocation(path)) + c, err := er.FileContentsByLocation(NewLocation(path)) assert.Nil(t, c) assert.Error(t, err) - m, err := er.FileMetadataByLocation(makeLocation(path)) + m, err := er.FileMetadataByLocation(NewLocation(path)) assert.Empty(t, m.LinkDestination) assert.Error(t, err) - l := er.RelativeFileByPath(makeLocation(""), path) + l := er.RelativeFileByPath(NewLocation(""), path) assert.Nil(t, l) } for _, path := range test.expected { assert.True(t, er.HasPath(path)) - c, err := er.FileContentsByLocation(makeLocation(path)) + c, err := er.FileContentsByLocation(NewLocation(path)) assert.NotNil(t, c) assert.Nil(t, err) - m, err := er.FileMetadataByLocation(makeLocation(path)) + m, err := er.FileMetadataByLocation(NewLocation(path)) assert.NotEmpty(t, m.LinkDestination) assert.Nil(t, err) - l := er.RelativeFileByPath(makeLocation(""), path) + l := er.RelativeFileByPath(NewLocation(""), path) assert.NotNil(t, l) } }) @@ -119,17 +117,6 @@ func difference(a, b []string) []string { return diff } -func makeLocation(path string) Location { - return Location{ - Coordinates: Coordinates{ - RealPath: path, - FileSystemID: "", - }, - VirtualPath: "", - ref: file.Reference{}, - } -} - func locationPaths(locations []Location) []string { paths := []string{} for _, l := range locations { @@ -145,7 +132,7 @@ type mockResolver struct { func (r *mockResolver) getLocations() ([]Location, error) { out := []Location{} for _, path := range r.locations { - out = append(out, makeLocation(path)) + out = append(out, NewLocation(path)) } return out, nil } @@ -189,11 +176,8 @@ func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) { } func (r *mockResolver) RelativeFileByPath(_ Location, path string) *Location { - return &Location{ - Coordinates: Coordinates{ - RealPath: path, - }, - } + l := NewLocation(path) + return &l } func (r *mockResolver) AllLocations() <-chan Location { diff --git a/syft/source/image_all_layers_resolver_test.go b/syft/source/image_all_layers_resolver_test.go index a94039e73..86892b188 100644 --- a/syft/source/image_all_layers_resolver_test.go +++ b/syft/source/image_all_layers_resolver_test.go @@ -398,66 +398,17 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/etc/group", - }, - VirtualPath: "/etc/group", - }, - { - Coordinates: Coordinates{ - RealPath: "/etc/passwd", - }, - VirtualPath: "/etc/passwd", - }, - { - Coordinates: Coordinates{ - RealPath: "/etc/shadow", - }, - VirtualPath: "/etc/shadow", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, + NewVirtualLocation("/etc/group", "/etc/group"), + NewVirtualLocation("/etc/passwd", "/etc/passwd"), + NewVirtualLocation("/etc/shadow", "/etc/shadow"), + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 // note: we're de-duping the redundant access to file-3.txt // ... (there would usually be two copies) - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1 + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2 }, }, { @@ -469,32 +420,10 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/link-1", - }, - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/link-within", - }, + NewVirtualLocation("/file-1.txt", "/link-1"), + NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1 + NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2 + NewVirtualLocation("/file-3.txt", "/link-within"), }, }, { @@ -506,20 +435,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 }, }, { @@ -531,45 +448,12 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, - // when we copy into the link path, the same file-4.txt is copied - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied }, }, { @@ -581,45 +465,12 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - // copy 1 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - // copy 2 - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, - // when we copy into the link path, the same file-4.txt is copied - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied }, }, { @@ -632,18 +483,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, + NewVirtualLocation("/file-2.txt", "/link-2"), + NewVirtualLocation("/file-2.txt", "/link-2"), }, }, { @@ -656,18 +497,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-indirect", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-indirect", - }, + NewVirtualLocation("/file-2.txt", "/link-indirect"), + NewVirtualLocation("/file-2.txt", "/link-indirect"), }, }, } diff --git a/syft/source/image_squash_resolver_test.go b/syft/source/image_squash_resolver_test.go index cfbeff167..106de9a5e 100644 --- a/syft/source/image_squash_resolver_test.go +++ b/syft/source/image_squash_resolver_test.go @@ -382,48 +382,13 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/etc/group", - }, - VirtualPath: "/etc/group", - }, - { - Coordinates: Coordinates{ - RealPath: "/etc/passwd", - }, - VirtualPath: "/etc/passwd", - }, - { - Coordinates: Coordinates{ - RealPath: "/etc/shadow", - }, - VirtualPath: "/etc/shadow", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/etc/group", "/etc/group"), + NewVirtualLocation("/etc/passwd", "/etc/passwd"), + NewVirtualLocation("/etc/shadow", "/etc/shadow"), + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { @@ -435,32 +400,14 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/link-1", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, + NewVirtualLocation("/file-1.txt", "/link-1"), + NewVirtualLocation("/file-2.txt", "/link-2"), + // though this is a link, and it matches to the file, the resolver de-duplicates files // by the real path, so it is not included in the results - //{ - // Coordinates: Coordinates{ - // RealPath: "/file-2.txt", - // }, - // VirtualPath: "/link-indirect", - //}, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/link-within", - }, + //NewVirtualLocation("/file-2.txt", "/link-indirect"), + + NewVirtualLocation("/file-3.txt", "/link-within"), }, }, { @@ -473,12 +420,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // this has two copies in the base image, which overwrites the same location - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, + NewVirtualLocation("/file-2.txt", "/file-2.txt"), }, }, { @@ -490,30 +432,10 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { @@ -524,34 +446,44 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ + { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/file-1.txt", + }, + VirtualPath: "/link-1", + ref: file.Reference{RealPath: "/file-1.txt"}, }, - VirtualPath: "/link-1", - ref: file.Reference{RealPath: "/file-1.txt"}, }, { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", + LocationData: LocationData{ + + Coordinates: Coordinates{ + RealPath: "/file-2.txt", + }, + VirtualPath: "/link-2", + ref: file.Reference{RealPath: "/file-2.txt"}, }, - VirtualPath: "/link-2", - ref: file.Reference{RealPath: "/file-2.txt"}, }, // we already have this real file path via another link, so only one is returned //{ - // Coordinates: Coordinates{ - // RealPath: "/file-2.txt", - // }, - // VirtualPath: "/link-indirect", - // ref: file.Reference{RealPath: "/file-2.txt"}, + // LocationData: LocationData{ + // Coordinates: Coordinates{ + // RealPath: "/file-2.txt", + // }, + // VirtualPath: "/link-indirect", + // ref: file.Reference{RealPath: "/file-2.txt"}, + // }, //}, { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/file-3.txt", + }, + VirtualPath: "/link-within", + ref: file.Reference{RealPath: "/file-3.txt"}, }, - VirtualPath: "/link-within", - ref: file.Reference{RealPath: "/file-3.txt"}, }, }, }, @@ -564,30 +496,10 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { return actualLocations }, expected: []Location{ - { - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/file-1.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/file-2.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/file-3.txt", - }, - { - Coordinates: Coordinates{ - RealPath: "/parent/file-4.txt", - }, - VirtualPath: "/parent/file-4.txt", - }, + NewVirtualLocation("/file-1.txt", "/file-1.txt"), + NewVirtualLocation("/file-2.txt", "/file-2.txt"), + NewVirtualLocation("/file-3.txt", "/file-3.txt"), + NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { @@ -600,12 +512,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - }, + NewVirtualLocation("/file-2.txt", "/link-2"), }, }, { @@ -618,12 +525,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { }, expected: []Location{ // we have multiple copies across layers - { - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-indirect", - }, + NewVirtualLocation("/file-2.txt", "/link-indirect"), }, }, } @@ -646,7 +548,8 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { func compareLocations(t *testing.T, expected, actual []Location) { t.Helper() - ignoreUnexported := cmpopts.IgnoreFields(Location{}, "ref") + ignoreUnexported := cmpopts.IgnoreFields(LocationData{}, "ref") + ignoreMetadata := cmpopts.IgnoreFields(LocationMetadata{}, "Annotations") ignoreFS := cmpopts.IgnoreFields(Coordinates{}, "FileSystemID") sort.Sort(Locations(expected)) @@ -655,6 +558,7 @@ func compareLocations(t *testing.T, expected, actual []Location) { if d := cmp.Diff(expected, actual, ignoreUnexported, ignoreFS, + ignoreMetadata, ); d != "" { t.Errorf("unexpected locations (-want +got):\n%s", d) diff --git a/syft/source/location.go b/syft/source/location.go index b4f1b267a..70e140a10 100644 --- a/syft/source/location.go +++ b/syft/source/location.go @@ -3,6 +3,8 @@ package source import ( "fmt" + "github.com/hashicorp/go-multierror" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" ) @@ -10,18 +12,55 @@ import ( // Location represents a path relative to a particular filesystem resolved to a specific file.Reference. This struct is used as a key // in content fetching to uniquely identify a file relative to a request (the VirtualPath). type Location struct { + LocationData `cyclonedx:""` + LocationMetadata `cyclonedx:""` +} + +type LocationData struct { Coordinates `cyclonedx:""` // Empty string here means there is no intermediate property name, e.g. syft:locations:0:path without "coordinates" // note: it is IMPORTANT to ignore anything but the coordinates for a Location when considering the ID (hash value) // since the coordinates are the minimally correct ID for a location (symlinks should not come into play) - VirtualPath string `hash:"ignore" json:"virtualPath,omitempty"` // The path to the file which may or may not have hardlinks / symlinks - ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location. + VirtualPath string `hash:"ignore" json:"-"` // The path to the file which may or may not have hardlinks / symlinks + ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location. +} + +type LocationMetadata struct { + Annotations map[string]string `json:"annotations,omitempty"` // Arbitrary key-value pairs that can be used to annotate a location +} + +func (m *LocationMetadata) merge(other LocationMetadata) error { + var errs error + for k, v := range other.Annotations { + if otherV, ok := m.Annotations[k]; ok { + if v != otherV { + err := fmt.Errorf("unable to merge location metadata: conflicting values for key=%q: %q != %q", k, v, otherV) + errs = multierror.Append(errs, err) + continue + } + } + m.Annotations[k] = v + } + return errs +} + +func (l Location) WithAnnotation(key, value string) Location { + if l.LocationMetadata.Annotations == nil { + l.LocationMetadata.Annotations = map[string]string{} + } + l.LocationMetadata.Annotations[key] = value + return l } // NewLocation creates a new Location representing a path without denoting a filesystem or FileCatalog reference. func NewLocation(realPath string) Location { return Location{ - Coordinates: Coordinates{ - RealPath: realPath, + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: realPath, + }, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, }, } } @@ -29,40 +68,70 @@ func NewLocation(realPath string) Location { // NewVirtualLocation creates a new location for a path accessed by a virtual path (a path with a symlink or hardlink somewhere in the path) func NewVirtualLocation(realPath, virtualPath string) Location { return Location{ - Coordinates: Coordinates{ - RealPath: realPath, + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: realPath, + }, + VirtualPath: virtualPath, }, - VirtualPath: virtualPath, - } + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, + }} } // NewLocationFromCoordinates creates a new location for the given Coordinates. func NewLocationFromCoordinates(coordinates Coordinates) Location { return Location{ - Coordinates: coordinates, - } + LocationData: LocationData{ + Coordinates: coordinates, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, + }} +} + +// NewVirtualLocationFromCoordinates creates a new location for the given Coordinates via a virtual path. +func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath string) Location { + return Location{ + LocationData: LocationData{ + Coordinates: coordinates, + VirtualPath: virtualPath, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, + }} } // NewLocationFromImage creates a new Location representing the given path (extracted from the ref) relative to the given image. func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location { layer := img.FileCatalog.Layer(ref) return Location{ - Coordinates: Coordinates{ - RealPath: string(ref.RealPath), - FileSystemID: layer.Metadata.Digest, + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: string(ref.RealPath), + FileSystemID: layer.Metadata.Digest, + }, + VirtualPath: virtualPath, + ref: ref, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, }, - VirtualPath: virtualPath, - ref: ref, } } // NewLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory. func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { return Location{ - Coordinates: Coordinates{ - RealPath: responsePath, + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: responsePath, + }, + ref: ref, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, }, - ref: ref, } } @@ -72,11 +141,16 @@ func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, r return NewLocationFromDirectory(responsePath, ref) } return Location{ - Coordinates: Coordinates{ - RealPath: responsePath, + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: responsePath, + }, + VirtualPath: virtualResponsePath, + ref: ref, + }, + LocationMetadata: LocationMetadata{ + Annotations: map[string]string{}, }, - VirtualPath: virtualResponsePath, - ref: ref, } } diff --git a/syft/source/location_set.go b/syft/source/location_set.go index c2e915921..86d184c76 100644 --- a/syft/source/location_set.go +++ b/syft/source/location_set.go @@ -4,10 +4,12 @@ import ( "sort" "github.com/mitchellh/hashstructure/v2" + + "github.com/anchore/syft/internal/log" ) type LocationSet struct { - set map[Location]struct{} + set map[LocationData]LocationMetadata } func NewLocationSet(locations ...Location) (s LocationSet) { @@ -20,10 +22,18 @@ func NewLocationSet(locations ...Location) (s LocationSet) { func (s *LocationSet) Add(locations ...Location) { if s.set == nil { - s.set = make(map[Location]struct{}) + s.set = make(map[LocationData]LocationMetadata) } for _, l := range locations { - s.set[l] = struct{}{} + if m, ok := s.set[l.LocationData]; ok { + err := m.merge(l.LocationMetadata) + if err != nil { + log.Debugf("partial merge of location metadata: %+v", err) + } + s.set[l.LocationData] = m + } else { + s.set[l.LocationData] = l.LocationMetadata + } } } @@ -32,7 +42,7 @@ func (s LocationSet) Remove(locations ...Location) { return } for _, l := range locations { - delete(s.set, l) + delete(s.set, l.LocationData) } } @@ -40,7 +50,7 @@ func (s LocationSet) Contains(l Location) bool { if s.set == nil { return false } - _, ok := s.set[l] + _, ok := s.set[l.LocationData] return ok } @@ -50,8 +60,11 @@ func (s LocationSet) ToSlice() []Location { } locations := make([]Location, len(s.set)) idx := 0 - for v := range s.set { - locations[idx] = v + for dir := range s.set { + locations[idx] = Location{ + LocationData: dir, + LocationMetadata: s.set[dir], + } idx++ } sort.Sort(Locations(locations)) diff --git a/syft/source/location_set_test.go b/syft/source/location_set_test.go index e9a98fe2b..b3d53ae58 100644 --- a/syft/source/location_set_test.go +++ b/syft/source/location_set_test.go @@ -12,35 +12,43 @@ import ( func TestLocationSet(t *testing.T) { etcHostsLinkVar := Location{ - Coordinates: Coordinates{ - RealPath: "/etc/hosts", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/etc/hosts", + FileSystemID: "a", + }, + VirtualPath: "/var/etc/hosts", }, - VirtualPath: "/var/etc/hosts", } etcHostsLinkHome := Location{ - Coordinates: Coordinates{ - RealPath: "/etc/hosts", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/etc/hosts", + FileSystemID: "a", + }, + VirtualPath: "/home/wagoodman/hosts", }, - VirtualPath: "/home/wagoodman/hosts", } binA := Location{ - Coordinates: Coordinates{ - RealPath: "/bin", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/bin", + FileSystemID: "a", + }, + VirtualPath: "/usr/bin", }, - VirtualPath: "/usr/bin", } binB := Location{ - Coordinates: Coordinates{ - RealPath: "/bin", - FileSystemID: "b", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/bin", + FileSystemID: "b", + }, + VirtualPath: "/usr/bin", }, - VirtualPath: "/usr/bin", } tests := []struct { @@ -87,41 +95,51 @@ func TestLocationSet(t *testing.T) { func TestLocationSet_Hash(t *testing.T) { etcAlink := Location{ - Coordinates: Coordinates{ - RealPath: "/etc/hosts", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/etc/hosts", + FileSystemID: "a", + }, + VirtualPath: "/var/etc/hosts", }, - VirtualPath: "/var/etc/hosts", } etcA := Location{ - Coordinates: Coordinates{ - RealPath: "/etc/hosts", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/etc/hosts", + FileSystemID: "a", + }, }, } etcB := Location{ - Coordinates: Coordinates{ - RealPath: "/etc/hosts", - FileSystemID: "b", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/etc/hosts", + FileSystemID: "b", + }, }, } binA := Location{ - Coordinates: Coordinates{ - RealPath: "/bin", - FileSystemID: "a", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/bin", + FileSystemID: "a", + }, + VirtualPath: "/usr/bin", }, - VirtualPath: "/usr/bin", } binB := Location{ - Coordinates: Coordinates{ - RealPath: "/bin", - FileSystemID: "b", + LocationData: LocationData{ + Coordinates: Coordinates{ + RealPath: "/bin", + FileSystemID: "b", + }, + VirtualPath: "/usr/bin", }, - VirtualPath: "/usr/bin", } tests := []struct { diff --git a/syft/source/location_test.go b/syft/source/location_test.go index 92b6ee45b..96f0e3fcd 100644 --- a/syft/source/location_test.go +++ b/syft/source/location_test.go @@ -37,9 +37,11 @@ func TestLocation_ID(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { l := Location{ - Coordinates: test.coordinates, - VirtualPath: test.virtualPath, - ref: test.ref, + LocationData: LocationData{ + Coordinates: test.coordinates, + VirtualPath: test.virtualPath, + ref: test.ref, + }, } assert.Equal(t, l.ID(), test.coordinates.ID()) }) diff --git a/syft/source/mock_resolver.go b/syft/source/mock_resolver.go index f0e95cc38..89960d6bd 100644 --- a/syft/source/mock_resolver.go +++ b/syft/source/mock_resolver.go @@ -18,7 +18,7 @@ var _ FileResolver = (*MockResolver)(nil) // paths, which are typically paths to test fixtures. type MockResolver struct { locations []Location - metadata map[Location]FileMetadata + metadata map[Coordinates]FileMetadata mimeTypeIndex map[string][]Location extension map[string][]Location basename map[string][]Location @@ -41,18 +41,19 @@ func NewMockResolverForPaths(paths ...string) *MockResolver { return &MockResolver{ locations: locations, - metadata: make(map[Location]FileMetadata), + metadata: make(map[Coordinates]FileMetadata), extension: extension, basename: basename, } } -func NewMockResolverForPathsWithMetadata(metadata map[Location]FileMetadata) *MockResolver { +func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]FileMetadata) *MockResolver { var locations []Location var mimeTypeIndex = make(map[string][]Location) extension := make(map[string][]Location) basename := make(map[string][]Location) - for l, m := range metadata { + for c, m := range metadata { + l := NewLocationFromCoordinates(c) locations = append(locations, l) mimeTypeIndex[m.MIMEType] = append(mimeTypeIndex[m.MIMEType], l) ext := path.Ext(l.RealPath) @@ -89,7 +90,7 @@ func (r MockResolver) String() string { // path does not exist, an error is returned. func (r MockResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { for _, l := range r.locations { - if l == location { + if l.Coordinates == location.Coordinates { return os.Open(location.RealPath) } } diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index 128b585aa..7ef25c783 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -254,3 +255,63 @@ func TestPkgCoverageCatalogerConfiguration(t *testing.T) { c.Catalogers = []string{"rust"} assert.Len(t, cataloger.ImageCatalogers(c), 0) } + +func TestPkgCoverageImage_HasEvidence(t *testing.T) { + sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil) + + var cases []testCase + cases = append(cases, commonTestCases...) + cases = append(cases, imageOnlyTestCases...) + + pkgTypesMissingEvidence := strset.New() + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + for a := range sbom.Artifacts.PackageCatalog.Enumerate(c.pkgType) { + assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type) + for _, l := range a.Locations.ToSlice() { + if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists { + pkgTypesMissingEvidence.Add(string(a.Type)) + t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type) + } + } + } + + }) + } + + if pkgTypesMissingEvidence.Size() > 0 { + t.Log("Package types missing evidence annotations (img resolver): ", pkgTypesMissingEvidence.List()) + } +} + +func TestPkgCoverageDirectory_HasEvidence(t *testing.T) { + sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage") + + var cases []testCase + cases = append(cases, commonTestCases...) + cases = append(cases, imageOnlyTestCases...) + + pkgTypesMissingEvidence := strset.New() + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + for a := range sbom.Artifacts.PackageCatalog.Enumerate(c.pkgType) { + assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type) + for _, l := range a.Locations.ToSlice() { + if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists { + pkgTypesMissingEvidence.Add(string(a.Type)) + t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type) + } + } + } + + }) + } + + if pkgTypesMissingEvidence.Size() > 0 { + t.Log("Package types missing evidence annotations (dir resolver): ", pkgTypesMissingEvidence.List()) + } +}