mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
Add relationships for dpkg packages (#2212)
* add relationships for deb packages Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update snapshots Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * bump json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * small refactor to remove duplicate code Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
0748945c83
commit
ef759038f5
22 changed files with 2541 additions and 42 deletions
|
@ -3,5 +3,5 @@ package internal
|
|||
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 = "11.0.0"
|
||||
JSONSchemaVersion = "11.0.1"
|
||||
)
|
||||
|
|
2003
schema/json/schema-11.0.1.json
Normal file
2003
schema/json/schema-11.0.1.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -57,7 +57,7 @@ func redactor(values ...string) testutils.Redactor {
|
|||
`sha256:[A-Za-z0-9]{64}`: `sha256:redacted`,
|
||||
|
||||
// BOM refs
|
||||
`bom-ref="[a-zA-Z0-9\-:]+"`: `bom-ref:redacted`,
|
||||
`bom-ref="[a-zA-Z0-9\-:]+"`: `bom-ref="redacted"`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3" type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=ad5013466727018f" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4" type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=f27313b22a5ba330" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -78,7 +78,7 @@
|
|||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -92,7 +92,7 @@
|
|||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -214,7 +214,7 @@
|
|||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -61,7 +61,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -97,6 +97,6 @@ Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef
|
|||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ FilesAnalyzed: false
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
SPDXID: SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -50,6 +50,6 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
|||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -53,6 +53,6 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
|||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "db4abfe497c180d3",
|
||||
"id": "ad5013466727018f",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "9fd0b9f41034991d",
|
||||
"id": "aa0ca2c331576dfd",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "958443e2d9304af4",
|
||||
"id": "f27313b22a5ba330",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
|
|
@ -44,6 +44,11 @@ func TestDpkgCataloger(t *testing.T) {
|
|||
Contains configuration files and directories required for
|
||||
authentication to work on Debian systems. This package is required
|
||||
on almost all installations.`,
|
||||
Depends: []string{
|
||||
"debconf (>= 0.5) | debconf-2.0",
|
||||
"debconf (>= 1.5.19) | cdebconf",
|
||||
"libpam-modules (>= 1.0.1-6)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/pam.conf",
|
||||
|
@ -112,6 +117,7 @@ func TestDpkgCataloger(t *testing.T) {
|
|||
SQLite is a C library that implements an SQL database engine.
|
||||
Programs that link with the SQLite library can have SQL database
|
||||
access without running a separate RDBMS process.`,
|
||||
Depends: []string{"libc6 (>= 2.29)"},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{Path: "/usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
|
|
|
@ -36,13 +36,15 @@ func newDpkgPackage(d pkg.DpkgMetadata, dbLocation file.Location, resolver file.
|
|||
Metadata: d,
|
||||
}
|
||||
|
||||
// the current entry only has what may have been listed in the status file, however, there are additional
|
||||
// files that are listed in multiple other locations. We should retrieve them all and merge the file lists
|
||||
// together.
|
||||
mergeFileListing(resolver, dbLocation, &p)
|
||||
if resolver != nil {
|
||||
// the current entry only has what may have been listed in the status file, however, there are additional
|
||||
// files that are listed in multiple other locations. We should retrieve them all and merge the file lists
|
||||
// together.
|
||||
mergeFileListing(resolver, dbLocation, &p)
|
||||
|
||||
// fetch additional data from the copyright file to derive the license information
|
||||
addLicenses(resolver, dbLocation, &p)
|
||||
// fetch additional data from the copyright file to derive the license information
|
||||
addLicenses(resolver, dbLocation, &p)
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ func parseDpkgDB(resolver file.Resolver, env *generic.Environment, reader file.L
|
|||
pkgs = append(pkgs, newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease))
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, associateRelationships(pkgs), nil
|
||||
}
|
||||
|
||||
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
|
||||
|
@ -63,6 +63,22 @@ func parseDpkgStatus(reader io.Reader) ([]pkg.DpkgMetadata, error) {
|
|||
return metadata, nil
|
||||
}
|
||||
|
||||
// dpkgExtractedMetadata is an adapter struct to capture the fields from the dpkg status file, however, the final
|
||||
// pkg.DpkgMetadata struct has different types for some fields (e.g. Provides, Depends, and PreDepends is []string, not a string).
|
||||
type dpkgExtractedMetadata struct {
|
||||
Package string `mapstructure:"Package"`
|
||||
Source string `mapstructure:"Source"`
|
||||
Version string `mapstructure:"Version"`
|
||||
SourceVersion string `mapstructure:"SourceVersion"`
|
||||
Architecture string `mapstructure:"Architecture"`
|
||||
Maintainer string `mapstructure:"Maintainer"`
|
||||
InstalledSize int `mapstructure:"InstalledSize"`
|
||||
Description string `mapstructure:"Description"`
|
||||
Provides string `mapstructure:"Provides"`
|
||||
Depends string `mapstructure:"Depends"`
|
||||
PreDepends string `mapstructure:"PreDepends"` // note: original doc is Pre-Depends
|
||||
}
|
||||
|
||||
// parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader.
|
||||
func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
||||
var retErr error
|
||||
|
@ -77,22 +93,36 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
|||
retErr = err
|
||||
}
|
||||
|
||||
entry := pkg.DpkgMetadata{}
|
||||
err = mapstructure.Decode(dpkgFields, &entry)
|
||||
raw := dpkgExtractedMetadata{}
|
||||
err = mapstructure.Decode(dpkgFields, &raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceName, sourceVersion := extractSourceVersion(entry.Source)
|
||||
sourceName, sourceVersion := extractSourceVersion(raw.Source)
|
||||
if sourceVersion != "" {
|
||||
entry.SourceVersion = sourceVersion
|
||||
entry.Source = sourceName
|
||||
raw.SourceVersion = sourceVersion
|
||||
raw.Source = sourceName
|
||||
}
|
||||
|
||||
if entry.Package == "" {
|
||||
if raw.Package == "" {
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
entry := pkg.DpkgMetadata{
|
||||
Package: raw.Package,
|
||||
Source: raw.Source,
|
||||
Version: raw.Version,
|
||||
SourceVersion: raw.SourceVersion,
|
||||
Architecture: raw.Architecture,
|
||||
Maintainer: raw.Maintainer,
|
||||
InstalledSize: raw.InstalledSize,
|
||||
Description: raw.Description,
|
||||
Provides: splitPkgList(raw.Provides),
|
||||
Depends: splitPkgList(raw.Depends),
|
||||
PreDepends: splitPkgList(raw.PreDepends),
|
||||
}
|
||||
|
||||
// there may be an optional conffiles section that we should persist as files
|
||||
if conffilesSection, exists := dpkgFields["Conffiles"]; exists && conffilesSection != nil {
|
||||
if sectionStr, ok := conffilesSection.(string); ok {
|
||||
|
@ -108,6 +138,17 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
|||
return &entry, retErr
|
||||
}
|
||||
|
||||
func splitPkgList(pkgList string) (ret []string) {
|
||||
fields := strings.Split(pkgList, ",")
|
||||
for _, field := range fields {
|
||||
field = strings.TrimSpace(field)
|
||||
if field != "" {
|
||||
ret = append(ret, field)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func extractAllFields(reader *bufio.Reader) (map[string]interface{}, error) {
|
||||
dpkgFields := make(map[string]interface{})
|
||||
var key string
|
||||
|
@ -195,3 +236,79 @@ func handleNewKeyValue(line string) (key string, val interface{}, err error) {
|
|||
|
||||
return "", nil, fmt.Errorf("cannot parse field from line: '%s'", line)
|
||||
}
|
||||
|
||||
// associateRelationships will create relationships between packages based on the "Depends", "Pre-Depends", and "Provides"
|
||||
// fields for installed packages. if there is an installed package that has a dependency that is (somehow) not installed,
|
||||
// then that relationship (between the installed and uninstalled package) will NOT be created.
|
||||
func associateRelationships(pkgs []pkg.Package) (relationships []artifact.Relationship) {
|
||||
// map["provides" + "package"] -> packages that provide that package
|
||||
lookup := make(map[string][]pkg.Package)
|
||||
|
||||
// read provided and add as keys for lookup keys as well as package names
|
||||
for _, p := range pkgs {
|
||||
meta, ok := p.Metadata.(pkg.DpkgMetadata)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract dpkg 'provides' metadata for package %+v", p.Name)
|
||||
continue
|
||||
}
|
||||
lookup[p.Name] = append(lookup[p.Name], p)
|
||||
for _, provides := range meta.Provides {
|
||||
k := stripVersionSpecifier(provides)
|
||||
lookup[k] = append(lookup[k], p)
|
||||
}
|
||||
}
|
||||
|
||||
// read "Depends" and "Pre-Depends" and match with keys
|
||||
for _, p := range pkgs {
|
||||
meta, ok := p.Metadata.(pkg.DpkgMetadata)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract dpkg 'dependency' metadata for package %+v", p.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var allDeps []string
|
||||
allDeps = append(allDeps, meta.Depends...)
|
||||
allDeps = append(allDeps, meta.PreDepends...)
|
||||
|
||||
for _, depSpecifier := range allDeps {
|
||||
deps := splitPackageChoice(depSpecifier)
|
||||
for _, dep := range deps {
|
||||
for _, depPkg := range lookup[dep] {
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: depPkg,
|
||||
To: p,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return relationships
|
||||
}
|
||||
|
||||
func stripVersionSpecifier(s string) string {
|
||||
// examples:
|
||||
// libgmp10 (>= 2:6.2.1+dfsg1) --> libgmp10
|
||||
// libgmp10 --> libgmp10
|
||||
// foo [i386] --> foo
|
||||
// default-mta | mail-transport-agent --> default-mta | mail-transport-agent
|
||||
// kernel-headers-2.2.10 [!hurd-i386] --> kernel-headers-2.2.10
|
||||
|
||||
items := internal.SplitAny(s, "[(<>=")
|
||||
if len(items) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
return strings.TrimSpace(items[0])
|
||||
}
|
||||
|
||||
func splitPackageChoice(s string) (ret []string) {
|
||||
fields := strings.Split(s, "|")
|
||||
for _, field := range fields {
|
||||
field = strings.TrimSpace(field)
|
||||
if field != "" {
|
||||
ret = append(ret, stripVersionSpecifier(field))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -11,9 +11,11 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
|
@ -48,6 +50,18 @@ func Test_parseDpkgStatus(t *testing.T) {
|
|||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
Provides: []string{"apt-transport-https (= 1.8.2)"},
|
||||
Depends: []string{
|
||||
"adduser",
|
||||
"gpgv | gpgv2 | gpgv1",
|
||||
"debian-archive-keyring",
|
||||
"libapt-pkg5.0 (>= 1.7.0~alpha3~)",
|
||||
"libc6 (>= 2.15)",
|
||||
"libgcc1 (>= 1:3.0)",
|
||||
"libgnutls30 (>= 3.6.6)",
|
||||
"libseccomp2 (>= 1.0.1)",
|
||||
"libstdc++6 (>= 5.2)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/apt/apt.conf.d/01autoremove",
|
||||
|
@ -110,6 +124,18 @@ func Test_parseDpkgStatus(t *testing.T) {
|
|||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
Provides: []string{"apt-transport-https (= 1.8.2)"},
|
||||
Depends: []string{
|
||||
"adduser",
|
||||
"gpgv | gpgv2 | gpgv1",
|
||||
"debian-archive-keyring",
|
||||
"libapt-pkg5.0 (>= 1.7.0~alpha3~)",
|
||||
"libc6 (>= 2.15)",
|
||||
"libgcc1 (>= 1:3.0)",
|
||||
"libgnutls30 (>= 3.6.6)",
|
||||
"libseccomp2 (>= 1.0.1)",
|
||||
"libstdc++6 (>= 5.2)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
},
|
||||
|
@ -135,7 +161,9 @@ func Test_parseDpkgStatus(t *testing.T) {
|
|||
globe. It is updated periodically to reflect changes made by
|
||||
political bodies to time zone boundaries, UTC offsets, and
|
||||
daylight-saving rules.`,
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
Provides: []string{"tzdata-buster"},
|
||||
Depends: []string{"debconf (>= 0.5) | debconf-2.0"},
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
{
|
||||
Package: "util-linux",
|
||||
|
@ -149,6 +177,14 @@ func Test_parseDpkgStatus(t *testing.T) {
|
|||
important utilities included in this package allow you to view kernel
|
||||
messages, create new filesystems, view block device information,
|
||||
interface with real time clock, etc.`,
|
||||
Depends: []string{"fdisk", "login (>= 1:4.5-1.1~)"},
|
||||
PreDepends: []string{
|
||||
"libaudit1 (>= 1:2.2.1)", "libblkid1 (>= 2.31.1)", "libc6 (>= 2.25)",
|
||||
"libcap-ng0 (>= 0.7.9)", "libmount1 (>= 2.25)", "libpam0g (>= 0.99.7.1)",
|
||||
"libselinux1 (>= 2.6-3~)", "libsmartcols1 (>= 2.33)", "libsystemd0",
|
||||
"libtinfo6 (>= 6)", "libudev1 (>= 183)", "libuuid1 (>= 2.16)",
|
||||
"zlib1g (>= 1:1.1.4)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/default/hwclock",
|
||||
|
@ -397,3 +433,122 @@ func Test_handleNewKeyValue(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_stripVersionSpecifier(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "package name only",
|
||||
input: "test",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "with version",
|
||||
input: "test (1.2.3)",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "multiple packages",
|
||||
input: "test | other",
|
||||
want: "test | other",
|
||||
},
|
||||
{
|
||||
name: "with architecture specifiers",
|
||||
input: "test [amd64 i386]",
|
||||
want: "test",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, stripVersionSpecifier(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_associateRelationships(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantRelationships map[string][]string
|
||||
}{
|
||||
{
|
||||
name: "relationships for coreutils",
|
||||
fixture: "test-fixtures/status/coreutils-relationships",
|
||||
wantRelationships: map[string][]string{
|
||||
"coreutils": {"libacl1", "libattr1", "libc6", "libgmp10", "libselinux1"},
|
||||
"libacl1": {"libc6"},
|
||||
"libattr1": {"libc6"},
|
||||
"libc6": {"libgcc-s1"},
|
||||
"libgcc-s1": {"gcc-12-base", "libc6"},
|
||||
"libgmp10": {"libc6"},
|
||||
"libpcre2-8-0": {"libc6"},
|
||||
"libselinux1": {"libc6", "libpcre2-8-0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relationships from dpkg example docs",
|
||||
fixture: "test-fixtures/status/doc-examples",
|
||||
wantRelationships: map[string][]string{
|
||||
"made-up-package-1": {"kernel-headers-2.2.10", "hurd-dev", "gnumach-dev"},
|
||||
"made-up-package-2": {"libluajit5.1-dev", "liblua5.1-dev"},
|
||||
"made-up-package-3": {"foo", "bar"},
|
||||
// note that the "made-up-package-4" depends on "made-up-package-5" but not via the direct
|
||||
// package name, but through the "provides" virtual package name "virtual-package-5".
|
||||
"made-up-package-4": {"made-up-package-5"},
|
||||
// note that though there is a "default-mta | mail-transport-agent | not-installed"
|
||||
// dependency choice we raise up the packages that are installed for every choice.
|
||||
// In this case that means that "default-mta" and "mail-transport-agent".
|
||||
"mutt": {"libc6", "default-mta", "mail-transport-agent"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relationships for libpam-runtime",
|
||||
fixture: "test-fixtures/status/libpam-runtime",
|
||||
wantRelationships: map[string][]string{
|
||||
"libpam-runtime": {"debconf1", "debconf-2.0", "debconf2", "cdebconf", "libpam-modules"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := os.Open(tt.fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
reader := file.NewLocationReadCloser(file.NewLocation(tt.fixture), f)
|
||||
|
||||
pkgs, relationships, err := parseDpkgDB(nil, &generic.Environment{}, reader)
|
||||
require.NotEmpty(t, pkgs)
|
||||
require.NotEmpty(t, relationships)
|
||||
require.NoError(t, err)
|
||||
|
||||
if d := cmp.Diff(tt.wantRelationships, abstractRelationships(t, relationships)); d != "" {
|
||||
t.Errorf("unexpected relationships (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func abstractRelationships(t testing.TB, relationships []artifact.Relationship) map[string][]string {
|
||||
t.Helper()
|
||||
|
||||
abstracted := make(map[string][]string)
|
||||
for _, relationship := range relationships {
|
||||
fromPkg, ok := relationship.From.(pkg.Package)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
toPkg, ok := relationship.To.(pkg.Package)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// we build this backwards since we use DependencyOfRelationship instead of DependsOn
|
||||
abstracted[toPkg.Name] = append(abstracted[toPkg.Name], fromPkg.Name)
|
||||
}
|
||||
|
||||
return abstracted
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
Package: coreutils
|
||||
Essential: yes
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
Section: utils
|
||||
Installed-Size: 20272
|
||||
Maintainer: Michael Stone <mstone@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: foreign
|
||||
Version: 9.1-1
|
||||
Pre-Depends: libacl1 (>= 2.2.23), libattr1 (>= 1:2.4.44), libc6 (>= 2.34), libgmp10 (>= 2:6.2.1+dfsg1), libselinux1 (>= 3.1~)
|
||||
|
||||
Package: libacl1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 101
|
||||
Maintainer: Guillem Jover <guillem@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: acl
|
||||
Version: 2.3.1-3
|
||||
Depends: libc6 (>= 2.33)
|
||||
|
||||
Package: libc6
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 23127
|
||||
Maintainer: GNU Libc Maintainers <debian-glibc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: glibc
|
||||
Version: 2.36-9+deb12u1
|
||||
Depends: libgcc-s1
|
||||
Recommends: libidn2-0 (>= 2.0.5~)
|
||||
Suggests: glibc-doc, debconf | debconf-2.0, libc-l10n, locales, libnss-nis, libnss-nisplus
|
||||
Breaks: aide (<< 0.17.3-4+b3), busybox (<< 1.30.1-6), chrony (<< 4.2-3~), fakechroot (<< 2.19-3.5), firefox (<< 91~), firefox-esr (<< 91~), gnumach-image-1.8-486 (<< 2:1.8+git20210923~), gnumach-image-1.8-486-dbg (<< 2:1.8+git20210923~), gnumach-image-1.8-xen-486 (<< 2:1.8+git20210923~), gnumach-image-1.8-xen-486-dbg (<< 2:1.8+git20210923~), hurd (<< 1:0.9.git20220301-2), ioquake3 (<< 1.36+u20200211.f2c61c1~dfsg-2~), iraf-fitsutil (<< 2018.07.06-4), libgegl-0.4-0 (<< 0.4.18), libtirpc1 (<< 0.2.3), locales (<< 2.36), locales-all (<< 2.36), macs (<< 2.2.7.1-3~), nocache (<< 1.1-1~), nscd (<< 2.36), openarena (<< 0.8.8+dfsg-4~), openssh-server (<< 1:8.1p1-5), python3-iptables (<< 1.0.0-2), r-cran-later (<< 0.7.5+dfsg-2), tinydns (<< 1:1.05-14), valgrind (<< 1:3.19.0-1~), wcc (<< 0.0.2+dfsg-3)
|
||||
|
||||
Package: libgcc-s1
|
||||
Protected: yes
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 147
|
||||
Maintainer: Debian GCC Maintainers <debian-gcc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gcc-12
|
||||
Version: 12.2.0-14
|
||||
Replaces: libgcc1 (<< 1:10)
|
||||
Provides: libgcc1 (= 1:12.2.0-14)
|
||||
Depends: gcc-12-base (= 12.2.0-14), libc6 (>= 2.35)
|
||||
|
||||
Package: gcc-12-base
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 100
|
||||
Maintainer: Debian GCC Maintainers <debian-gcc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gcc-12
|
||||
Version: 12.2.0-14
|
||||
Breaks: gnat (<< 7)
|
||||
|
||||
Package: libattr1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 99
|
||||
Maintainer: Guillem Jover <guillem@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: attr
|
||||
Version: 1:2.5.1-4
|
||||
Depends: libc6 (>= 2.17)
|
||||
|
||||
Package: libgmp10
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 855
|
||||
Maintainer: Debian Science Team <debian-science-maintainers@lists.alioth.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gmp
|
||||
Version: 2:6.2.1+dfsg1-1.1
|
||||
Depends: libc6 (>= 2.17)
|
||||
Breaks: libmath-gmp-perl (<< 2.20-1), libmath-prime-util-gmp-perl (<< 0.51-2), postgresql-pgmp (<< 1.0.3-1)
|
||||
|
||||
Package: libselinux1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 223
|
||||
Maintainer: Debian SELinux maintainers <selinux-devel@lists.alioth.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: libselinux (3.4-1)
|
||||
Version: 3.4-1+b6
|
||||
Depends: libc6 (>= 2.34), libpcre2-8-0 (>= 10.22)
|
||||
|
||||
Package: libpcre2-8-0
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 649
|
||||
Maintainer: Matthew Vernon <matthew@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: pcre2
|
||||
Version: 10.42-1
|
||||
Depends: libc6 (>= 2.34)
|
46
syft/pkg/cataloger/deb/test-fixtures/status/doc-examples
Normal file
46
syft/pkg/cataloger/deb/test-fixtures/status/doc-examples
Normal file
|
@ -0,0 +1,46 @@
|
|||
Package: mutt
|
||||
Version: 1.3.17-1
|
||||
Depends: libc6 (>= 2.2.1), default-mta | mail-transport-agent | not-installed
|
||||
|
||||
Package: made-up-package-1
|
||||
Version: 1.0.0
|
||||
Source: glibc
|
||||
Depends: kernel-headers-2.2.10 [!hurd-i386],
|
||||
hurd-dev [hurd-i386], gnumach-dev [hurd-i386]
|
||||
|
||||
Package: made-up-package-2
|
||||
Version: 2.0.0
|
||||
Depends:
|
||||
libluajit5.1-dev [i386 amd64 kfreebsd-i386 armel armhf powerpc mips],
|
||||
liblua5.1-dev [hurd-i386 ia64 kfreebsd-amd64 s390x sparc],
|
||||
|
||||
Package: made-up-package-3
|
||||
Version: 3.0.0
|
||||
Depends: foo [i386], bar [amd64]
|
||||
|
||||
Package: made-up-package-4
|
||||
Version: 3.0.0
|
||||
Depends: virtual-package-5
|
||||
|
||||
Package: made-up-package-5
|
||||
Provides: virtual-package-5 (=1.0)
|
||||
|
||||
Package: foo
|
||||
|
||||
Package: bar
|
||||
|
||||
Package: kernel-headers-2.2.10
|
||||
|
||||
Package: hurd-dev
|
||||
|
||||
Package: gnumach-dev
|
||||
|
||||
Package: default-mta
|
||||
|
||||
Package: mail-transport-agent
|
||||
|
||||
Package: libc6
|
||||
|
||||
Package: libluajit5.1-dev
|
||||
|
||||
Package: liblua5.1-dev
|
23
syft/pkg/cataloger/deb/test-fixtures/status/libpam-runtime
Normal file
23
syft/pkg/cataloger/deb/test-fixtures/status/libpam-runtime
Normal file
|
@ -0,0 +1,23 @@
|
|||
Package: libpam-runtime
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
Section: admin
|
||||
Installed-Size: 876
|
||||
Maintainer: Sam Hartman <hartmans@debian.org>
|
||||
Architecture: all
|
||||
Multi-Arch: foreign
|
||||
Source: pam
|
||||
Version: 1.5.2-6+deb12u1
|
||||
Replaces: libpam0g-dev, libpam0g-util
|
||||
Depends: debconf1 (>= 0.5) | debconf-2.0, debconf2 (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6)
|
||||
Conflicts: libpam0g-util
|
||||
|
||||
Package: debconf1
|
||||
|
||||
Package: debconf2
|
||||
|
||||
Package: debconf-2.0
|
||||
|
||||
Package: cdebconf
|
||||
|
||||
Package: libpam-modules
|
|
@ -14,16 +14,49 @@ var _ FileOwner = (*DpkgMetadata)(nil)
|
|||
|
||||
// DpkgMetadata represents all captured data for a Debian package DB entry; available fields are described
|
||||
// at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section.
|
||||
// Additional information about how these fields are used can be found at
|
||||
// - https://www.debian.org/doc/debian-policy/ch-controlfields.html
|
||||
// - https://www.debian.org/doc/debian-policy/ch-relationships.html
|
||||
// - https://www.debian.org/doc/debian-policy/ch-binary.html#s-virtual-pkg
|
||||
// - https://www.debian.org/doc/debian-policy/ch-relationships.html#s-virtual
|
||||
|
||||
type DpkgMetadata struct {
|
||||
Package string `mapstructure:"Package" json:"package"`
|
||||
Source string `mapstructure:"Source" json:"source" cyclonedx:"source"`
|
||||
Version string `mapstructure:"Version" json:"version"`
|
||||
SourceVersion string `mapstructure:"SourceVersion" json:"sourceVersion" cyclonedx:"sourceVersion"`
|
||||
Architecture string `mapstructure:"Architecture" json:"architecture"`
|
||||
Maintainer string `mapstructure:"Maintainer" json:"maintainer"`
|
||||
InstalledSize int `mapstructure:"InstalledSize" json:"installedSize" cyclonedx:"installedSize"`
|
||||
Description string `mapstructure:"Description" hash:"ignore" json:"-"`
|
||||
Files []DpkgFileRecord `json:"files"`
|
||||
Package string `json:"package"`
|
||||
Source string `json:"source" cyclonedx:"source"`
|
||||
Version string `json:"version"`
|
||||
SourceVersion string `json:"sourceVersion" cyclonedx:"sourceVersion"`
|
||||
|
||||
// Architecture can include the following sets of values depending on context and the control file used:
|
||||
// - a unique single word identifying a Debian machine architecture as described in Architecture specification string (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec) .
|
||||
// - an architecture wildcard identifying a set of Debian machine architectures, see Architecture wildcards (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-wildcard-spec). any matches all Debian machine architectures and is the most frequently used.
|
||||
// - "all", which indicates an architecture-independent package.
|
||||
// - "source", which indicates a source package.
|
||||
Architecture string `json:"architecture"`
|
||||
|
||||
// Maintainer is the package maintainer’s name and email address. The name must come first, then the email
|
||||
// address inside angle brackets <> (in RFC822 format).
|
||||
Maintainer string `json:"maintainer"`
|
||||
|
||||
InstalledSize int `json:"installedSize" cyclonedx:"installedSize"`
|
||||
|
||||
// Description contains a description of the binary package, consisting of two parts, the synopsis or the short
|
||||
// description, and the long description (in a multiline format).
|
||||
Description string `hash:"ignore" json:"-"`
|
||||
|
||||
// Provides is a virtual package that is provided by one or more packages. A virtual package is one which appears
|
||||
// in the Provides control field of another package. The effect is as if the package(s) which provide a particular
|
||||
// virtual package name had been listed by name everywhere the virtual package name appears. (See also Virtual packages)
|
||||
Provides []string `json:"provides,omitempty"`
|
||||
|
||||
// Depends This declares an absolute dependency. A package will not be configured unless all of the packages listed in
|
||||
// its Depends field have been correctly configured (unless there is a circular dependency).
|
||||
Depends []string `json:"depends,omitempty"`
|
||||
|
||||
// PreDepends is like Depends, except that it also forces dpkg to complete installation of the packages named
|
||||
// before even starting the installation of the package which declares the pre-dependency.
|
||||
PreDepends []string `json:"preDepends,omitempty"`
|
||||
|
||||
Files []DpkgFileRecord `json:"files"`
|
||||
}
|
||||
|
||||
// DpkgFileRecord represents a single file attributed to a debian package.
|
||||
|
|
Loading…
Reference in a new issue