diff --git a/internal/constants.go b/internal/constants.go index c5dcea13a2d..2390e6da83f 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.3" + JSONSchemaVersion = "7.1.4" ) diff --git a/schema/json/schema-7.1.4.json b/schema/json/schema-7.1.4.json new file mode 100644 index 00000000000..2f3f4543531 --- /dev/null +++ b/schema/json/schema-7.1.4.json @@ -0,0 +1,1772 @@ +{ + "$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" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "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" + ] + }, + "LinuxKernelMetadata": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModuleMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "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/LinuxKernelMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelModuleMetadata" + }, + { + "$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/artifact/relationship.go b/syft/artifact/relationship.go index 0428192d15c..eee7f316e47 100644 --- a/syft/artifact/relationship.go +++ b/syft/artifact/relationship.go @@ -7,6 +7,12 @@ const ( // has been completed. OwnershipByFileOverlapRelationship RelationshipType = "ownership-by-file-overlap" + // EvidentByRelationship is a package-to-file relationship indicating the that existence of this package is evident + // by the contents of a file. This does not necessarily mean that the package is contained within that file + // or that it is described by it (either or both may be true). This does NOT map to an existing specific SPDX + // relationship. Instead, this should be mapped to OTHER and the comment field be updated to show EVIDENT_BY. + EvidentByRelationship RelationshipType = "evident-by" + // ContainsRelationship (supports any-to-any linkages) is a proxy for the SPDX 2.2 CONTAINS relationship. ContainsRelationship RelationshipType = "contains" diff --git a/syft/formats/common/spdxhelpers/to_format_model.go b/syft/formats/common/spdxhelpers/to_format_model.go index 1640f5b0fee..d75c6ba13ad 100644 --- a/syft/formats/common/spdxhelpers/to_format_model.go +++ b/syft/formats/common/spdxhelpers/to_format_model.go @@ -408,6 +408,8 @@ func lookupRelationship(ty artifact.RelationshipType) (bool, RelationshipType, s return true, DependencyOfRelationship, "" case artifact.OwnershipByFileOverlapRelationship: return true, OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty) + case artifact.EvidentByRelationship: + return true, OtherRelationship, fmt.Sprintf("%s: indicates the package's existence is evident by the given file", ty) } return false, "", "" } diff --git a/syft/formats/common/spdxhelpers/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go index ddd4a1261cf..20914c4f41b 100644 --- a/syft/formats/common/spdxhelpers/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -209,6 +209,12 @@ func Test_lookupRelationship(t *testing.T) { ty: OtherRelationship, comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", }, + { + input: artifact.EvidentByRelationship, + exists: true, + ty: OtherRelationship, + comment: "evident-by: indicates the package's existence is evident by the given file", + }, { input: "made-up", exists: false, diff --git a/syft/formats/common/spdxhelpers/to_syft_model.go b/syft/formats/common/spdxhelpers/to_syft_model.go index e66d014a2d4..2c15a697f35 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model.go +++ b/syft/formats/common/spdxhelpers/to_syft_model.go @@ -176,9 +176,16 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [ var to artifact.Identifiable var typ artifact.RelationshipType if toLocationOk { - if r.Relationship == string(ContainsRelationship) { + switch RelationshipType(r.Relationship) { + case ContainsRelationship: typ = artifact.ContainsRelationship to = toLocation + case OtherRelationship: + // Encoding uses a specifically formatted comment... + if strings.Index(r.RelationshipComment, string(artifact.EvidentByRelationship)) == 0 { + typ = artifact.EvidentByRelationship + to = toLocation + } } } else { switch RelationshipType(r.Relationship) { @@ -188,7 +195,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [ case OtherRelationship: // Encoding uses a specifically formatted comment... if strings.Index(r.RelationshipComment, string(artifact.OwnershipByFileOverlapRelationship)) == 0 { - typ = artifact.DependencyOfRelationship + typ = artifact.OwnershipByFileOverlapRelationship to = toPackage } } diff --git a/syft/formats/common/spdxhelpers/to_syft_model_test.go b/syft/formats/common/spdxhelpers/to_syft_model_test.go index bfe57280e51..b7dbadb4b61 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model_test.go +++ b/syft/formats/common/spdxhelpers/to_syft_model_test.go @@ -4,9 +4,11 @@ import ( "testing" "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/spdx/v2/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) @@ -307,3 +309,113 @@ func TestH1Digest(t *testing.T) { }) } } + +func Test_toSyftRelationships(t *testing.T) { + type args struct { + spdxIDMap map[string]interface{} + doc *spdx.Document + } + + pkg1 := pkg.Package{ + Name: "github.com/googleapis/gnostic", + Version: "v0.5.5", + } + pkg1.SetID() + + pkg2 := pkg.Package{ + Name: "rfc3339", + Version: "1.2", + Type: pkg.RpmPkg, + } + pkg2.SetID() + + pkg3 := pkg.Package{ + Name: "rfc3339", + Version: "1.2", + Type: pkg.PythonPkg, + } + pkg3.SetID() + + loc1 := source.NewLocationFromCoordinates(source.Coordinates{ + RealPath: "/somewhere/real", + FileSystemID: "abc", + }) + + tests := []struct { + name string + args args + want []artifact.Relationship + }{ + { + name: "evident-by relationship", + args: args{ + spdxIDMap: map[string]interface{}{ + string(toSPDXID(pkg1)): &pkg1, + string(toSPDXID(loc1)): &loc1, + }, + doc: &spdx.Document{ + Relationships: []*spdx.Relationship{ + { + RefA: common.DocElementID{ + ElementRefID: toSPDXID(pkg1), + }, + RefB: common.DocElementID{ + ElementRefID: toSPDXID(loc1), + }, + Relationship: spdx.RelationshipOther, + RelationshipComment: "evident-by: indicates the package's existence is evident by the given file", + }, + }, + }, + }, + want: []artifact.Relationship{ + { + From: pkg1, + To: loc1, + Type: artifact.EvidentByRelationship, + }, + }, + }, + { + name: "ownership-by-file-overlap relationship", + args: args{ + spdxIDMap: map[string]interface{}{ + string(toSPDXID(pkg2)): &pkg2, + string(toSPDXID(pkg3)): &pkg3, + }, + doc: &spdx.Document{ + Relationships: []*spdx.Relationship{ + { + RefA: common.DocElementID{ + ElementRefID: toSPDXID(pkg2), + }, + RefB: common.DocElementID{ + ElementRefID: toSPDXID(pkg3), + }, + Relationship: spdx.RelationshipOther, + RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", + }, + }, + }, + }, + want: []artifact.Relationship{ + { + From: pkg2, + To: pkg3, + Type: artifact.OwnershipByFileOverlapRelationship, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := toSyftRelationships(tt.args.spdxIDMap, tt.args.doc) + require.Len(t, actual, len(tt.want)) + for i := range actual { + require.Equal(t, tt.want[i].From.ID(), actual[i].From.ID()) + require.Equal(t, tt.want[i].To.ID(), actual[i].To.ID()) + require.Equal(t, tt.want[i].Type, actual[i].Type) + } + }) + } +} diff --git a/syft/formats/syftjson/model/file.go b/syft/formats/syftjson/model/file.go index 1bed51e582e..796cecebf1c 100644 --- a/syft/formats/syftjson/model/file.go +++ b/syft/formats/syftjson/model/file.go @@ -20,4 +20,5 @@ type FileMetadataEntry struct { UserID int `json:"userID"` GroupID int `json:"groupID"` MIMEType string `json:"mimeType"` + Size int64 `json:"size"` } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index c5b476c563f..3288035324b 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.3", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json" + "version": "7.1.4", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 0fb0b7907db..322e8cb0fde 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -81,7 +81,8 @@ "type": "Directory", "userID": 0, "groupID": 0, - "mimeType": "" + "mimeType": "", + "size": 0 } }, { @@ -94,7 +95,8 @@ "type": "RegularFile", "userID": 0, "groupID": 0, - "mimeType": "" + "mimeType": "", + "size": 0 }, "contents": "the-contents", "digests": [ @@ -115,7 +117,8 @@ "linkDestination": "/c", "userID": 0, "groupID": 0, - "mimeType": "" + "mimeType": "", + "size": 0 } }, { @@ -128,7 +131,8 @@ "type": "RegularFile", "userID": 1, "groupID": 2, - "mimeType": "" + "mimeType": "", + "size": 0 }, "digests": [ { @@ -185,7 +189,7 @@ } }, "schema": { - "version": "7.1.3", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json" + "version": "7.1.4", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index 3ad08854386..eda15743f5f 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.3", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json" + "version": "7.1.4", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json" } } diff --git a/syft/formats/syftjson/to_format_model.go b/syft/formats/syftjson/to_format_model.go index 61ebe1007ef..b042f4c2951 100644 --- a/syft/formats/syftjson/to_format_model.go +++ b/syft/formats/syftjson/to_format_model.go @@ -144,6 +144,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe UserID: metadata.UserID, GroupID: metadata.GroupID, MIMEType: metadata.MIMEType, + Size: metadata.Size, } } diff --git a/syft/formats/syftjson/to_syft_model.go b/syft/formats/syftjson/to_syft_model.go index cbad53fb0fd..a4d1accf42c 100644 --- a/syft/formats/syftjson/to_syft_model.go +++ b/syft/formats/syftjson/to_syft_model.go @@ -1,13 +1,17 @@ package syftjson import ( + "os" + "strconv" "strings" "github.com/google/go-cmp/cmp" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/formats/syftjson/model" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" @@ -20,9 +24,13 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) { catalog := toSyftCatalog(doc.Artifacts, idAliases) + fileArtifacts := toSyftFiles(doc.Files) + return &sbom.SBOM{ Artifacts: sbom.Artifacts{ PackageCatalog: catalog, + FileMetadata: fileArtifacts.FileMetadata, + FileDigests: fileArtifacts.FileDigests, LinuxDistribution: toSyftLinuxRelease(doc.Distro), }, Source: *toSyftSourceData(doc.Source), @@ -31,6 +39,72 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) { }, nil } +func toSyftFiles(files []model.File) sbom.Artifacts { + ret := sbom.Artifacts{ + FileMetadata: make(map[source.Coordinates]source.FileMetadata), + FileDigests: make(map[source.Coordinates][]file.Digest), + } + + for _, f := range files { + coord := f.Location + if f.Metadata != nil { + mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64) + if err != nil { + log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err) + mode = 0 + } + + fm := os.FileMode(mode) + + ret.FileMetadata[coord] = source.FileMetadata{ + Path: coord.RealPath, + LinkDestination: f.Metadata.LinkDestination, + Size: f.Metadata.Size, + UserID: f.Metadata.UserID, + GroupID: f.Metadata.GroupID, + Type: toSyftFileType(f.Metadata.Type), + IsDir: fm.IsDir(), + Mode: fm, + MIMEType: f.Metadata.MIMEType, + } + } + + for _, d := range f.Digests { + ret.FileDigests[coord] = append(ret.FileDigests[coord], file.Digest{ + Algorithm: d.Algorithm, + Value: d.Value, + }) + } + } + + return ret +} + +func toSyftFileType(ty string) stereoscopeFile.Type { + switch ty { + case "SymbolicLink": + return stereoscopeFile.TypeSymLink + case "HardLink": + return stereoscopeFile.TypeHardLink + case "Directory": + return stereoscopeFile.TypeDirectory + case "Socket": + return stereoscopeFile.TypeSocket + case "BlockDevice": + return stereoscopeFile.TypeBlockDevice + case "CharacterDevice": + return stereoscopeFile.TypeCharacterDevice + case "FIFONode": + return stereoscopeFile.TypeFIFO + case "RegularFile": + return stereoscopeFile.TypeRegular + case "IrregularFile": + return stereoscopeFile.TypeIrregular + default: + return stereoscopeFile.TypeIrregular + } +} + func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release { if cmp.Equal(d, model.LinuxRelease{}) { return nil @@ -117,7 +191,7 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio typ := artifact.RelationshipType(relationship.Type) switch typ { - case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship: + case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship, artifact.EvidentByRelationship: default: if !strings.Contains(string(typ), "dependency-of") { log.Warnf("unknown relationship type: %s", typ) diff --git a/syft/formats/syftjson/to_syft_model_test.go b/syft/formats/syftjson/to_syft_model_test.go index 1e7b86c3d24..b3dcc2cffa3 100644 --- a/syft/formats/syftjson/to_syft_model_test.go +++ b/syft/formats/syftjson/to_syft_model_test.go @@ -6,8 +6,11 @@ import ( "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" + stereoFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/formats/syftjson/model" + "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" ) @@ -124,3 +127,104 @@ func Test_idsHaveChanged(t *testing.T) { assert.NotNil(t, to) assert.Equal(t, "pkg-2", to.Name) } + +func Test_toSyftFiles(t *testing.T) { + coord := source.Coordinates{ + RealPath: "/somerwhere/place", + FileSystemID: "abc", + } + + tests := []struct { + name string + files []model.File + want sbom.Artifacts + }{ + { + name: "empty", + files: []model.File{}, + want: sbom.Artifacts{ + FileMetadata: map[source.Coordinates]source.FileMetadata{}, + FileDigests: map[source.Coordinates][]file.Digest{}, + }, + }, + { + name: "no metadata", + files: []model.File{ + { + ID: string(coord.ID()), + Location: coord, + Metadata: nil, + Digests: []file.Digest{ + { + Algorithm: "sha256", + Value: "123", + }, + }, + }, + }, + want: sbom.Artifacts{ + FileMetadata: map[source.Coordinates]source.FileMetadata{}, + FileDigests: map[source.Coordinates][]file.Digest{ + coord: { + { + Algorithm: "sha256", + Value: "123", + }, + }, + }, + }, + }, + { + name: "single file", + files: []model.File{ + { + ID: string(coord.ID()), + Location: coord, + Metadata: &model.FileMetadataEntry{ + Mode: 777, + Type: "RegularFile", + LinkDestination: "", + UserID: 42, + GroupID: 32, + MIMEType: "text/plain", + Size: 92, + }, + Digests: []file.Digest{ + { + Algorithm: "sha256", + Value: "123", + }, + }, + }, + }, + want: sbom.Artifacts{ + FileMetadata: map[source.Coordinates]source.FileMetadata{ + coord: { + Path: coord.RealPath, + LinkDestination: "", + Size: 92, + UserID: 42, + GroupID: 32, + Type: stereoFile.TypeRegular, + IsDir: false, + Mode: 511, // 777 octal = 511 decimal + MIMEType: "text/plain", + }, + }, + FileDigests: map[source.Coordinates][]file.Digest{ + coord: { + { + Algorithm: "sha256", + Value: "123", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, toSyftFiles(tt.files)) + }) + } +} diff --git a/syft/pkg/relationships.go b/syft/pkg/relationships.go index fe73c1b0f7b..b0e248d8e83 100644 --- a/syft/pkg/relationships.go +++ b/syft/pkg/relationships.go @@ -2,7 +2,8 @@ package pkg import "github.com/anchore/syft/syft/artifact" -// TODO: as more relationships are added, this function signature will probably accommodate selection func NewRelationships(catalog *Catalog) []artifact.Relationship { - return RelationshipsByFileOwnership(catalog) + rels := RelationshipsByFileOwnership(catalog) + rels = append(rels, RelationshipsEvidentBy(catalog)...) + return rels } diff --git a/syft/pkg/relationships_evident_by.go b/syft/pkg/relationships_evident_by.go new file mode 100644 index 00000000000..74b7a6050a3 --- /dev/null +++ b/syft/pkg/relationships_evident_by.go @@ -0,0 +1,25 @@ +package pkg + +import ( + "github.com/anchore/syft/syft/artifact" +) + +func RelationshipsEvidentBy(catalog *Catalog) []artifact.Relationship { + var edges []artifact.Relationship + for _, p := range catalog.Sorted() { + for _, l := range p.Locations.ToSlice() { + if v, exists := l.Annotations[EvidenceAnnotationKey]; !exists || v != PrimaryEvidenceAnnotation { + // skip non-primary evidence from being expressed as a relationship. + // note: this may be configurable in the future. + continue + } + edges = append(edges, artifact.Relationship{ + From: p, + To: l.Coordinates, + Type: artifact.EvidentByRelationship, + }) + } + } + + return edges +} diff --git a/syft/pkg/relationships_evident_by_test.go b/syft/pkg/relationships_evident_by_test.go new file mode 100644 index 00000000000..fd81b4c3bb2 --- /dev/null +++ b/syft/pkg/relationships_evident_by_test.go @@ -0,0 +1,87 @@ +package pkg + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/source" +) + +func TestRelationshipsEvidentBy(t *testing.T) { + + c := NewCatalog() + + coordA := source.Coordinates{ + RealPath: "/somewhere/real", + FileSystemID: "abc", + } + coordC := source.Coordinates{ + RealPath: "/somewhere/real", + FileSystemID: "abc", + } + coordD := source.Coordinates{ + RealPath: "/somewhere/real", + FileSystemID: "abc", + } + pkgA := Package{ + Locations: source.NewLocationSet( + // added! + source.NewLocationFromCoordinates(coordA).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), + // ignored... + source.NewLocationFromCoordinates(coordC).WithAnnotation(EvidenceAnnotationKey, SupportingEvidenceAnnotation), + source.NewLocationFromCoordinates(coordD), + ), + } + pkgA.SetID() + c.Add(pkgA) + + coordB := source.Coordinates{ + RealPath: "/somewhere-else/real", + FileSystemID: "def", + } + pkgB := Package{ + Locations: source.NewLocationSet( + // added! + source.NewLocationFromCoordinates(coordB).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), + ), + } + pkgB.SetID() + c.Add(pkgB) + + tests := []struct { + name string + catalog *Catalog + want []artifact.Relationship + }{ + { + name: "go case", + catalog: c, + want: []artifact.Relationship{ + { + From: pkgB, + To: coordB, + Type: artifact.EvidentByRelationship, + }, + { + From: pkgA, + To: coordA, + Type: artifact.EvidentByRelationship, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := RelationshipsEvidentBy(tt.catalog) + require.Len(t, actual, len(tt.want)) + for i := range actual { + assert.Equal(t, tt.want[i].From.ID(), actual[i].From.ID(), "from mismatch at index %d", i) + assert.Equal(t, tt.want[i].To.ID(), actual[i].To.ID(), "to mismatch at index %d", i) + assert.Equal(t, tt.want[i].Type, actual[i].Type, "type mismatch at index %d", i) + } + }) + } +}