mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
feat: support top-level SPDX package and graph (#1934)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
parent
1e4d26f526
commit
9480f10ccd
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@ VERSION
|
|||||||
*.hpi
|
*.hpi
|
||||||
*.zip
|
*.zip
|
||||||
.idea/
|
.idea/
|
||||||
|
*.iml
|
||||||
*.log
|
*.log
|
||||||
.images
|
.images
|
||||||
.tmp/
|
.tmp/
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -59,6 +59,7 @@ require (
|
|||||||
github.com/charmbracelet/lipgloss v0.7.1
|
github.com/charmbracelet/lipgloss v0.7.1
|
||||||
github.com/dave/jennifer v1.6.1
|
github.com/dave/jennifer v1.6.1
|
||||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||||
|
github.com/docker/distribution v2.8.2+incompatible
|
||||||
github.com/docker/docker v24.0.5+incompatible
|
github.com/docker/docker v24.0.5+incompatible
|
||||||
github.com/github/go-spdx/v2 v2.1.2
|
github.com/github/go-spdx/v2 v2.1.2
|
||||||
github.com/gkampitakis/go-snaps v0.4.7
|
github.com/gkampitakis/go-snaps v0.4.7
|
||||||
@ -99,7 +100,6 @@ require (
|
|||||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import (
|
|||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DocumentName(srcMetadata source.Description) string {
|
func DocumentName(src source.Description) string {
|
||||||
if srcMetadata.Name != "" {
|
if src.Name != "" {
|
||||||
return srcMetadata.Name
|
return src.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
switch metadata := srcMetadata.Metadata.(type) {
|
switch metadata := src.Metadata.(type) {
|
||||||
case source.StereoscopeImageSourceMetadata:
|
case source.StereoscopeImageSourceMetadata:
|
||||||
return metadata.UserInput
|
return metadata.UserInput
|
||||||
case source.DirectorySourceMetadata:
|
case source.DirectorySourceMetadata:
|
||||||
|
|||||||
@ -17,13 +17,11 @@ func Test_DocumentName(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
inputName string
|
|
||||||
srcMetadata source.Description
|
srcMetadata source.Description
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
inputName: "my-name",
|
|
||||||
srcMetadata: source.Description{
|
srcMetadata: source.Description{
|
||||||
Metadata: source.StereoscopeImageSourceMetadata{
|
Metadata: source.StereoscopeImageSourceMetadata{
|
||||||
UserInput: "image-repo/name:tag",
|
UserInput: "image-repo/name:tag",
|
||||||
@ -34,21 +32,27 @@ func Test_DocumentName(t *testing.T) {
|
|||||||
expected: "image-repo/name:tag",
|
expected: "image-repo/name:tag",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "directory",
|
name: "directory",
|
||||||
inputName: "my-name",
|
|
||||||
srcMetadata: source.Description{
|
srcMetadata: source.Description{
|
||||||
Metadata: source.DirectorySourceMetadata{Path: "some/path/to/place"},
|
Metadata: source.DirectorySourceMetadata{Path: "some/path/to/place"},
|
||||||
},
|
},
|
||||||
expected: "some/path/to/place",
|
expected: "some/path/to/place",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "file",
|
name: "file",
|
||||||
inputName: "my-name",
|
|
||||||
srcMetadata: source.Description{
|
srcMetadata: source.Description{
|
||||||
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
|
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
|
||||||
},
|
},
|
||||||
expected: "some/path/to/place",
|
expected: "some/path/to/place",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "named",
|
||||||
|
srcMetadata: source.Description{
|
||||||
|
Name: "some/name",
|
||||||
|
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
|
||||||
|
},
|
||||||
|
expected: "some/name",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/spdx/tools-golang/spdx"
|
"github.com/spdx/tools-golang/spdx"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
@ -21,10 +23,20 @@ import (
|
|||||||
"github.com/anchore/syft/syft/formats/common/util"
|
"github.com/anchore/syft/syft/formats/common/util"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
noAssertion = "NOASSERTION"
|
noAssertion = "NOASSERTION"
|
||||||
|
|
||||||
|
spdxPrimaryPurposeContainer = "CONTAINER"
|
||||||
|
spdxPrimaryPurposeFile = "FILE"
|
||||||
|
spdxPrimaryPurposeOther = "OTHER"
|
||||||
|
|
||||||
|
prefixImage = "Image"
|
||||||
|
prefixDirectory = "Directory"
|
||||||
|
prefixFile = "File"
|
||||||
|
prefixUnknown = "Unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToFormatModel creates and populates a new SPDX document struct that follows the SPDX 2.3
|
// ToFormatModel creates and populates a new SPDX document struct that follows the SPDX 2.3
|
||||||
@ -33,23 +45,37 @@ const (
|
|||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||||
name, namespace := DocumentNameAndNamespace(s.Source)
|
name, namespace := DocumentNameAndNamespace(s.Source)
|
||||||
|
|
||||||
|
packages := toPackages(s.Artifacts.Packages, s)
|
||||||
|
|
||||||
relationships := toRelationships(s.RelationshipsSorted())
|
relationships := toRelationships(s.RelationshipsSorted())
|
||||||
|
|
||||||
// for valid SPDX we need a document describes relationship
|
// for valid SPDX we need a document describes relationship
|
||||||
// TODO: remove this placeholder after deciding on correct behavior
|
describesID := spdx.ElementID("DOCUMENT")
|
||||||
// for the primary package purpose field:
|
|
||||||
// https://spdx.github.io/spdx-spec/v2.3/package-information/#724-primary-package-purpose-field
|
rootPackage := toRootPackage(s.Source)
|
||||||
|
if rootPackage != nil {
|
||||||
|
describesID = rootPackage.PackageSPDXIdentifier
|
||||||
|
|
||||||
|
// add all relationships from the document root to all other packages
|
||||||
|
relationships = append(relationships, toRootRelationships(rootPackage, packages)...)
|
||||||
|
|
||||||
|
// append the root package
|
||||||
|
packages = append(packages, rootPackage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a relationship for the package the document describes
|
||||||
documentDescribesRelationship := &spdx.Relationship{
|
documentDescribesRelationship := &spdx.Relationship{
|
||||||
RefA: spdx.DocElementID{
|
RefA: spdx.DocElementID{
|
||||||
ElementRefID: "DOCUMENT",
|
ElementRefID: "DOCUMENT",
|
||||||
},
|
},
|
||||||
Relationship: string(DescribesRelationship),
|
Relationship: string(DescribesRelationship),
|
||||||
RefB: spdx.DocElementID{
|
RefB: spdx.DocElementID{
|
||||||
ElementRefID: "DOCUMENT",
|
ElementRefID: describesID,
|
||||||
},
|
},
|
||||||
RelationshipComment: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add the root document relationship
|
||||||
relationships = append(relationships, documentDescribesRelationship)
|
relationships = append(relationships, documentDescribesRelationship)
|
||||||
|
|
||||||
return &spdx.Document{
|
return &spdx.Document{
|
||||||
@ -123,19 +149,130 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
|||||||
// Cardinality: optional, one
|
// Cardinality: optional, one
|
||||||
CreatorComment: "",
|
CreatorComment: "",
|
||||||
},
|
},
|
||||||
Packages: toPackages(s.Artifacts.Packages, s),
|
Packages: packages,
|
||||||
Files: toFiles(s),
|
Files: toFiles(s),
|
||||||
Relationships: relationships,
|
Relationships: relationships,
|
||||||
OtherLicenses: toOtherLicenses(s.Artifacts.Packages),
|
OtherLicenses: toOtherLicenses(s.Artifacts.Packages),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toRootRelationships(rootPackage *spdx.Package, packages []*spdx.Package) (out []*spdx.Relationship) {
|
||||||
|
for _, p := range packages {
|
||||||
|
out = append(out, &spdx.Relationship{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: rootPackage.PackageSPDXIdentifier,
|
||||||
|
},
|
||||||
|
Relationship: string(ContainsRelationship),
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: p.PackageSPDXIdentifier,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
|
func toRootPackage(s source.Description) *spdx.Package {
|
||||||
|
var prefix string
|
||||||
|
|
||||||
|
name := s.Name
|
||||||
|
version := s.Version
|
||||||
|
|
||||||
|
var purl *packageurl.PackageURL
|
||||||
|
purpose := ""
|
||||||
|
var checksums []spdx.Checksum
|
||||||
|
switch m := s.Metadata.(type) {
|
||||||
|
case source.StereoscopeImageSourceMetadata:
|
||||||
|
prefix = prefixImage
|
||||||
|
purpose = spdxPrimaryPurposeContainer
|
||||||
|
|
||||||
|
qualifiers := packageurl.Qualifiers{
|
||||||
|
{
|
||||||
|
Key: "arch",
|
||||||
|
Value: m.Architecture,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, _ := reference.Parse(m.UserInput)
|
||||||
|
if ref, ok := ref.(reference.NamedTagged); ok {
|
||||||
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
|
Key: "tag",
|
||||||
|
Value: ref.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c := toChecksum(m.ManifestDigest)
|
||||||
|
if c != nil {
|
||||||
|
checksums = append(checksums, *c)
|
||||||
|
purl = &packageurl.PackageURL{
|
||||||
|
Type: "oci",
|
||||||
|
Name: s.Name,
|
||||||
|
Version: m.ManifestDigest,
|
||||||
|
Qualifiers: qualifiers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case source.DirectorySourceMetadata:
|
||||||
|
prefix = prefixDirectory
|
||||||
|
purpose = spdxPrimaryPurposeFile
|
||||||
|
|
||||||
|
case source.FileSourceMetadata:
|
||||||
|
prefix = prefixFile
|
||||||
|
purpose = spdxPrimaryPurposeFile
|
||||||
|
|
||||||
|
for _, d := range m.Digests {
|
||||||
|
checksums = append(checksums, spdx.Checksum{
|
||||||
|
Algorithm: toChecksumAlgorithm(d.Algorithm),
|
||||||
|
Value: d.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
prefix = prefixUnknown
|
||||||
|
purpose = spdxPrimaryPurposeOther
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = s.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &spdx.Package{
|
||||||
|
PackageName: name,
|
||||||
|
PackageSPDXIdentifier: spdx.ElementID(SanitizeElementID(fmt.Sprintf("DocumentRoot-%s-%s", prefix, name))),
|
||||||
|
PackageVersion: version,
|
||||||
|
PackageChecksums: checksums,
|
||||||
|
PackageSupplier: nil,
|
||||||
|
PackageExternalReferences: nil,
|
||||||
|
PrimaryPackagePurpose: purpose,
|
||||||
|
}
|
||||||
|
|
||||||
|
if purl != nil {
|
||||||
|
p.PackageExternalReferences = []*spdx.PackageExternalReference{
|
||||||
|
{
|
||||||
|
Category: string(PackageManagerReferenceCategory),
|
||||||
|
RefType: string(PurlExternalRefType),
|
||||||
|
Locator: purl.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
||||||
maxLen := 40
|
maxLen := 40
|
||||||
id := ""
|
id := ""
|
||||||
switch it := identifiable.(type) {
|
switch it := identifiable.(type) {
|
||||||
case pkg.Package:
|
case pkg.Package:
|
||||||
id = SanitizeElementID(fmt.Sprintf("Package-%s-%s-%s", it.Type, it.Name, it.ID()))
|
switch {
|
||||||
|
case it.Type != "" && it.Name != "":
|
||||||
|
id = fmt.Sprintf("Package-%s-%s-%s", it.Type, it.Name, it.ID())
|
||||||
|
case it.Name != "":
|
||||||
|
id = fmt.Sprintf("Package-%s-%s", it.Name, it.ID())
|
||||||
|
case it.Type != "":
|
||||||
|
id = fmt.Sprintf("Package-%s-%s", it.Type, it.ID())
|
||||||
|
default:
|
||||||
|
id = fmt.Sprintf("Package-%s", it.ID())
|
||||||
|
}
|
||||||
case file.Coordinates:
|
case file.Coordinates:
|
||||||
p := ""
|
p := ""
|
||||||
parts := strings.Split(it.RealPath, "/")
|
parts := strings.Split(it.RealPath, "/")
|
||||||
@ -150,12 +287,12 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
|||||||
}
|
}
|
||||||
p = path.Join(part, p)
|
p = path.Join(part, p)
|
||||||
}
|
}
|
||||||
id = SanitizeElementID(fmt.Sprintf("File-%s-%s", p, it.ID()))
|
id = fmt.Sprintf("File-%s-%s", p, it.ID())
|
||||||
default:
|
default:
|
||||||
id = string(identifiable.ID())
|
id = string(identifiable.ID())
|
||||||
}
|
}
|
||||||
// NOTE: the spdx library prepend SPDXRef-, so we don't do it here
|
// NOTE: the spdx library prepend SPDXRef-, so we don't do it here
|
||||||
return spdx.ElementID(id)
|
return spdx.ElementID(SanitizeElementID(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
|
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||||
@ -494,6 +631,18 @@ func toFileChecksums(digests []file.Digest) (checksums []spdx.Checksum) {
|
|||||||
return checksums
|
return checksums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toChecksum takes a checksum in the format <algorithm>:<hash> and returns an spdx.Checksum or nil if the string is invalid
|
||||||
|
func toChecksum(algorithmHash string) *spdx.Checksum {
|
||||||
|
parts := strings.Split(algorithmHash, ":")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &spdx.Checksum{
|
||||||
|
Algorithm: toChecksumAlgorithm(parts[0]),
|
||||||
|
Value: parts[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toChecksumAlgorithm(algorithm string) spdx.ChecksumAlgorithm {
|
func toChecksumAlgorithm(algorithm string) spdx.ChecksumAlgorithm {
|
||||||
// this needs to be an uppercase version of our algorithm
|
// this needs to be an uppercase version of our algorithm
|
||||||
return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||||
|
|||||||
@ -5,17 +5,247 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/spdx/tools-golang/spdx"
|
"github.com/spdx/tools-golang/spdx"
|
||||||
|
"github.com/spdx/tools-golang/spdx/v2/v2_3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/sourcemetadata"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Add ToFormatModel tests
|
func Test_toFormatModel(t *testing.T) {
|
||||||
|
tracker := sourcemetadata.NewCompletionTester(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in sbom.SBOM
|
||||||
|
expected *spdx.Document
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "container",
|
||||||
|
in: sbom.SBOM{
|
||||||
|
Source: source.Description{
|
||||||
|
Name: "alpine",
|
||||||
|
Version: "sha256:d34db33f",
|
||||||
|
Metadata: source.StereoscopeImageSourceMetadata{
|
||||||
|
UserInput: "alpine:latest",
|
||||||
|
ManifestDigest: "sha256:d34db33f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
Packages: pkg.NewCollection(pkg.Package{
|
||||||
|
Name: "pkg-1",
|
||||||
|
Version: "version-1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &spdx.Document{
|
||||||
|
SPDXIdentifier: "DOCUMENT",
|
||||||
|
SPDXVersion: spdx.Version,
|
||||||
|
DataLicense: spdx.DataLicense,
|
||||||
|
DocumentName: "alpine",
|
||||||
|
|
||||||
|
Packages: []*spdx.Package{
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||||
|
PackageName: "pkg-1",
|
||||||
|
PackageVersion: "version-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "DocumentRoot-Image-alpine",
|
||||||
|
PackageName: "alpine",
|
||||||
|
PackageVersion: "sha256:d34db33f",
|
||||||
|
PrimaryPackagePurpose: "CONTAINER",
|
||||||
|
PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}},
|
||||||
|
PackageExternalReferences: []*v2_3.PackageExternalReference{
|
||||||
|
{
|
||||||
|
Category: "PACKAGE-MANAGER",
|
||||||
|
RefType: "purl",
|
||||||
|
Locator: "pkg:oci/alpine@sha256:d34db33f?arch=&tag=latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Relationships: []*spdx.Relationship{
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-Image-alpine",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "Package-pkg-1-pkg-1",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipContains,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DOCUMENT",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-Image-alpine",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipDescribes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "directory",
|
||||||
|
in: sbom.SBOM{
|
||||||
|
Source: source.Description{
|
||||||
|
Name: "some/directory",
|
||||||
|
Metadata: source.DirectorySourceMetadata{
|
||||||
|
Path: "some/directory",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
Packages: pkg.NewCollection(pkg.Package{
|
||||||
|
Name: "pkg-1",
|
||||||
|
Version: "version-1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &spdx.Document{
|
||||||
|
SPDXIdentifier: "DOCUMENT",
|
||||||
|
SPDXVersion: spdx.Version,
|
||||||
|
DataLicense: spdx.DataLicense,
|
||||||
|
DocumentName: "some/directory",
|
||||||
|
|
||||||
|
Packages: []*spdx.Package{
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||||
|
PackageName: "pkg-1",
|
||||||
|
PackageVersion: "version-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "DocumentRoot-Directory-some-directory",
|
||||||
|
PackageName: "some/directory",
|
||||||
|
PackageVersion: "",
|
||||||
|
PrimaryPackagePurpose: "FILE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Relationships: []*spdx.Relationship{
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-Directory-some-directory",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "Package-pkg-1-pkg-1",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipContains,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DOCUMENT",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-Directory-some-directory",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipDescribes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
in: sbom.SBOM{
|
||||||
|
Source: source.Description{
|
||||||
|
Name: "path/to/some.file",
|
||||||
|
Version: "sha256:d34db33f",
|
||||||
|
Metadata: source.FileSourceMetadata{
|
||||||
|
Path: "path/to/some.file",
|
||||||
|
Digests: []file.Digest{
|
||||||
|
{
|
||||||
|
Algorithm: "sha256",
|
||||||
|
Value: "d34db33f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
Packages: pkg.NewCollection(pkg.Package{
|
||||||
|
Name: "pkg-1",
|
||||||
|
Version: "version-1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &spdx.Document{
|
||||||
|
SPDXIdentifier: "DOCUMENT",
|
||||||
|
SPDXVersion: spdx.Version,
|
||||||
|
DataLicense: spdx.DataLicense,
|
||||||
|
DocumentName: "path/to/some.file",
|
||||||
|
|
||||||
|
Packages: []*spdx.Package{
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||||
|
PackageName: "pkg-1",
|
||||||
|
PackageVersion: "version-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PackageSPDXIdentifier: "DocumentRoot-File-path-to-some.file",
|
||||||
|
PackageName: "path/to/some.file",
|
||||||
|
PackageVersion: "sha256:d34db33f",
|
||||||
|
PrimaryPackagePurpose: "FILE",
|
||||||
|
PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Relationships: []*spdx.Relationship{
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-File-path-to-some.file",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "Package-pkg-1-pkg-1",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipContains,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefA: spdx.DocElementID{
|
||||||
|
ElementRefID: "DOCUMENT",
|
||||||
|
},
|
||||||
|
RefB: spdx.DocElementID{
|
||||||
|
ElementRefID: "DocumentRoot-File-path-to-some.file",
|
||||||
|
},
|
||||||
|
Relationship: spdx.RelationshipDescribes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
tracker.Tested(t, test.in.Source.Metadata)
|
||||||
|
|
||||||
|
// replace IDs with package names
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
for p := range test.in.Artifacts.Packages.Enumerate() {
|
||||||
|
p.OverrideID(artifact.ID(p.Name))
|
||||||
|
pkgs = append(pkgs, p)
|
||||||
|
}
|
||||||
|
test.in.Artifacts.Packages = pkg.NewCollection(pkgs...)
|
||||||
|
|
||||||
|
// convert
|
||||||
|
got := ToFormatModel(test.in)
|
||||||
|
|
||||||
|
// check differences
|
||||||
|
if diff := cmp.Diff(test.expected, got,
|
||||||
|
cmpopts.IgnoreUnexported(spdx.Document{}, spdx.Package{}),
|
||||||
|
cmpopts.IgnoreFields(spdx.Document{}, "CreationInfo", "DocumentNamespace"),
|
||||||
|
cmpopts.IgnoreFields(spdx.Package{}, "PackageDownloadLocation", "IsFilesAnalyzedTagPresent", "PackageSourceInfo", "PackageLicenseConcluded", "PackageLicenseDeclared", "PackageCopyrightText"),
|
||||||
|
); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_toPackageChecksums(t *testing.T) {
|
func Test_toPackageChecksums(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -2,11 +2,15 @@ package spdxhelpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/spdx"
|
"github.com/spdx/tools-golang/spdx"
|
||||||
|
"github.com/spdx/tools-golang/spdx/v2/common"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
@ -26,12 +30,10 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
|
|||||||
return nil, errors.New("cannot convert SPDX document to Syft model because document is nil")
|
return nil, errors.New("cannot convert SPDX document to Syft model because document is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
spdxIDMap := make(map[string]interface{})
|
spdxIDMap := make(map[string]any)
|
||||||
|
|
||||||
src := extractSourceFromNamespace(doc.DocumentNamespace)
|
|
||||||
|
|
||||||
s := &sbom.SBOM{
|
s := &sbom.SBOM{
|
||||||
Source: src,
|
Source: extractSource(spdxIDMap, doc),
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
Packages: pkg.NewCollection(),
|
Packages: pkg.NewCollection(),
|
||||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||||
@ -40,7 +42,7 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
collectSyftPackages(s, spdxIDMap, doc)
|
collectSyftPackages(s, spdxIDMap, doc.Packages)
|
||||||
|
|
||||||
collectSyftFiles(s, spdxIDMap, doc)
|
collectSyftFiles(s, spdxIDMap, doc)
|
||||||
|
|
||||||
@ -49,6 +51,166 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDirectory(name string) bool {
|
||||||
|
if name == "." || name == ".." || strings.HasSuffix(name, "/") || !strings.Contains(path.Base(name), ".") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePackage(packages []*spdx.Package, remove *spdx.Package) (pkgs []*spdx.Package) {
|
||||||
|
for _, p := range packages {
|
||||||
|
if p == remove {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, p)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeRelationships(relationships []*spdx.Relationship, spdxID spdx.ElementID) (relations []*spdx.Relationship) {
|
||||||
|
for _, r := range relationships {
|
||||||
|
if r.RefA.ElementRefID == spdxID || r.RefB.ElementRefID == spdxID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
relations = append(relations, r)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func findRootPackages(doc *spdx.Document) (out []*spdx.Package) {
|
||||||
|
for _, p := range doc.Packages {
|
||||||
|
for _, r := range doc.Relationships {
|
||||||
|
describes := r.RefA.ElementRefID == "DOCUMENT" &&
|
||||||
|
r.Relationship == spdx.RelationshipDescribes &&
|
||||||
|
r.RefB.ElementRefID == p.PackageSPDXIdentifier
|
||||||
|
|
||||||
|
describedBy := r.RefB.ElementRefID == "DOCUMENT" &&
|
||||||
|
r.Relationship == spdx.RelationshipDescribedBy &&
|
||||||
|
r.RefA.ElementRefID == p.PackageSPDXIdentifier
|
||||||
|
|
||||||
|
if !describes && !describedBy {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractSource(spdxIDMap map[string]any, doc *spdx.Document) source.Description {
|
||||||
|
src := extractSourceFromNamespace(doc.DocumentNamespace)
|
||||||
|
|
||||||
|
rootPackages := findRootPackages(doc)
|
||||||
|
|
||||||
|
if len(rootPackages) != 1 {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
p := rootPackages[0]
|
||||||
|
|
||||||
|
switch p.PrimaryPackagePurpose {
|
||||||
|
case spdxPrimaryPurposeContainer:
|
||||||
|
src = containerSource(p)
|
||||||
|
case spdxPrimaryPurposeFile:
|
||||||
|
src = fileSource(p)
|
||||||
|
default:
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
spdxIDMap[string(p.PackageSPDXIdentifier)] = src
|
||||||
|
|
||||||
|
doc.Packages = removePackage(doc.Packages, p)
|
||||||
|
doc.Relationships = removeRelationships(doc.Relationships, p.PackageSPDXIdentifier)
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerSource(p *spdx.Package) source.Description {
|
||||||
|
id := string(p.PackageSPDXIdentifier)
|
||||||
|
|
||||||
|
container := p.PackageName
|
||||||
|
v := p.PackageVersion
|
||||||
|
if v != "" {
|
||||||
|
container += ":" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := ""
|
||||||
|
if len(p.PackageChecksums) > 0 {
|
||||||
|
c := p.PackageChecksums[0]
|
||||||
|
digest = fmt.Sprintf("%s:%s", fromChecksumAlgorithm(c.Algorithm), c.Value)
|
||||||
|
}
|
||||||
|
return source.Description{
|
||||||
|
ID: id,
|
||||||
|
Name: p.PackageName,
|
||||||
|
Version: p.PackageVersion,
|
||||||
|
Metadata: source.StereoscopeImageSourceMetadata{
|
||||||
|
UserInput: container,
|
||||||
|
ID: id,
|
||||||
|
Layers: nil, // TODO handle formats with nested layer packages like Tern and K8s BOM tool
|
||||||
|
ManifestDigest: digest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileSource(p *spdx.Package) source.Description {
|
||||||
|
typeRegex := regexp.MustCompile("^DocumentRoot-([^-]+)-.*$")
|
||||||
|
typeName := typeRegex.ReplaceAllString(string(p.PackageSPDXIdentifier), "$1")
|
||||||
|
|
||||||
|
var version string
|
||||||
|
var metadata any
|
||||||
|
switch {
|
||||||
|
case typeName == prefixDirectory:
|
||||||
|
// is a Syft SBOM, explicitly a directory source
|
||||||
|
metadata, version = directorySourceMetadata(p)
|
||||||
|
case typeName == prefixFile:
|
||||||
|
// is a Syft SBOM, explicitly a file source
|
||||||
|
metadata, version = fileSourceMetadata(p)
|
||||||
|
case isDirectory(p.PackageName):
|
||||||
|
// is a non-Syft SBOM, which looks like a directory
|
||||||
|
metadata, version = directorySourceMetadata(p)
|
||||||
|
default:
|
||||||
|
// is a non-Syft SBOM, which is probably a file
|
||||||
|
metadata, version = fileSourceMetadata(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.Description{
|
||||||
|
ID: string(p.PackageSPDXIdentifier),
|
||||||
|
Name: p.PackageName,
|
||||||
|
Version: version,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileSourceMetadata(p *spdx.Package) (any, string) {
|
||||||
|
version := p.PackageVersion
|
||||||
|
|
||||||
|
m := source.FileSourceMetadata{
|
||||||
|
Path: p.PackageName,
|
||||||
|
}
|
||||||
|
// if this is a Syft SBOM, we might have output a digest as the version
|
||||||
|
checksum := toChecksum(p.PackageVersion)
|
||||||
|
for _, d := range p.PackageChecksums {
|
||||||
|
if checksum != nil && checksum.Value == d.Value {
|
||||||
|
version = ""
|
||||||
|
}
|
||||||
|
m.Digests = append(m.Digests, file.Digest{
|
||||||
|
Algorithm: fromChecksumAlgorithm(d.Algorithm),
|
||||||
|
Value: d.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, version
|
||||||
|
}
|
||||||
|
|
||||||
|
func directorySourceMetadata(p *spdx.Package) (any, string) {
|
||||||
|
return source.DirectorySourceMetadata{
|
||||||
|
Path: p.PackageName,
|
||||||
|
Base: "",
|
||||||
|
}, p.PackageVersion
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE(jonas): SPDX doesn't inform what an SBOM is about,
|
// NOTE(jonas): SPDX doesn't inform what an SBOM is about,
|
||||||
// image, directory, for example. This is our best effort to determine
|
// image, directory, for example. This is our best effort to determine
|
||||||
// the scheme. Syft-generated SBOMs have in the namespace
|
// the scheme. Syft-generated SBOMs have in the namespace
|
||||||
@ -114,15 +276,15 @@ func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document) {
|
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, packages []*spdx.Package) {
|
||||||
for _, p := range doc.Packages {
|
for _, p := range packages {
|
||||||
syftPkg := toSyftPackage(p)
|
syftPkg := toSyftPackage(p)
|
||||||
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
||||||
s.Artifacts.Packages.Add(*syftPkg)
|
s.Artifacts.Packages.Add(syftPkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document) {
|
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) {
|
||||||
for _, f := range doc.Files {
|
for _, f := range doc.Files {
|
||||||
l := toSyftLocation(f)
|
l := toSyftLocation(f)
|
||||||
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
||||||
@ -135,13 +297,17 @@ func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.
|
|||||||
func toFileDigests(f *spdx.File) (digests []file.Digest) {
|
func toFileDigests(f *spdx.File) (digests []file.Digest) {
|
||||||
for _, digest := range f.Checksums {
|
for _, digest := range f.Checksums {
|
||||||
digests = append(digests, file.Digest{
|
digests = append(digests, file.Digest{
|
||||||
Algorithm: string(digest.Algorithm),
|
Algorithm: fromChecksumAlgorithm(digest.Algorithm),
|
||||||
Value: digest.Value,
|
Value: digest.Value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return digests
|
return digests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fromChecksumAlgorithm(algorithm common.ChecksumAlgorithm) string {
|
||||||
|
return strings.ToLower(string(algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
func toFileMetadata(f *spdx.File) (meta file.Metadata) {
|
func toFileMetadata(f *spdx.File) (meta file.Metadata) {
|
||||||
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
||||||
for _, typ := range f.FileTypes {
|
for _, typ := range f.FileTypes {
|
||||||
@ -164,21 +330,21 @@ func toFileMetadata(f *spdx.File) (meta file.Metadata) {
|
|||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) []artifact.Relationship {
|
func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifact.Relationship {
|
||||||
var out []artifact.Relationship
|
var out []artifact.Relationship
|
||||||
for _, r := range doc.Relationships {
|
for _, r := range doc.Relationships {
|
||||||
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
||||||
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) {
|
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) {
|
||||||
log.Debugf("ignoring relationship to external document: %+v", r)
|
log.Debugf("ignoring relationship to external document: %+v", r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
a := spdxIDMap[string(r.RefA.ElementRefID)]
|
a := spdxIDMap[string(r.RefA.ElementRefID)]
|
||||||
b := spdxIDMap[string(r.RefB.ElementRefID)]
|
b := spdxIDMap[string(r.RefB.ElementRefID)]
|
||||||
from, fromOk := a.(*pkg.Package)
|
from, fromOk := a.(pkg.Package)
|
||||||
toPackage, toPackageOk := b.(*pkg.Package)
|
toPackage, toPackageOk := b.(pkg.Package)
|
||||||
toLocation, toLocationOk := b.(*file.Location)
|
toLocation, toLocationOk := b.(file.Location)
|
||||||
if !fromOk || !(toPackageOk || toLocationOk) {
|
if !fromOk || !(toPackageOk || toLocationOk) {
|
||||||
log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
|
log.Debugf("unable to find valid relationship mapping from SPDX, ignoring: (from: %+v) (to: %+v)", a, b)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var to artifact.Identifiable
|
var to artifact.Identifiable
|
||||||
@ -234,9 +400,9 @@ func toSyftCoordinates(f *spdx.File) file.Coordinates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftLocation(f *spdx.File) *file.Location {
|
func toSyftLocation(f *spdx.File) file.Location {
|
||||||
l := file.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
|
l := file.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
|
||||||
return &l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireAndTrimPrefix(val interface{}, prefix string) string {
|
func requireAndTrimPrefix(val interface{}, prefix string) string {
|
||||||
@ -280,16 +446,16 @@ func extractPkgInfo(p *spdx.Package) pkgInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftPackage(p *spdx.Package) *pkg.Package {
|
func toSyftPackage(p *spdx.Package) pkg.Package {
|
||||||
info := extractPkgInfo(p)
|
info := extractPkgInfo(p)
|
||||||
metadataType, metadata := extractMetadata(p, info)
|
metadataType, metadata := extractMetadata(p, info)
|
||||||
sP := pkg.Package{
|
sP := &pkg.Package{
|
||||||
Type: info.typ,
|
Type: info.typ,
|
||||||
Name: p.PackageName,
|
Name: p.PackageName,
|
||||||
Version: p.PackageVersion,
|
Version: p.PackageVersion,
|
||||||
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
||||||
CPEs: extractCPEs(p),
|
CPEs: extractCPEs(p),
|
||||||
PURL: info.purl.String(),
|
PURL: purlValue(info.purl),
|
||||||
Language: info.lang,
|
Language: info.lang,
|
||||||
MetadataType: metadataType,
|
MetadataType: metadataType,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
@ -297,7 +463,15 @@ func toSyftPackage(p *spdx.Package) *pkg.Package {
|
|||||||
|
|
||||||
sP.SetID()
|
sP.SetID()
|
||||||
|
|
||||||
return &sP
|
return *sP
|
||||||
|
}
|
||||||
|
|
||||||
|
func purlValue(purl packageurl.PackageURL) string {
|
||||||
|
p := purl.String()
|
||||||
|
if p == "pkg:/" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
||||||
@ -384,7 +558,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
|||||||
case pkg.JavaPkg:
|
case pkg.JavaPkg:
|
||||||
var digests []file.Digest
|
var digests []file.Digest
|
||||||
for _, value := range p.PackageChecksums {
|
for _, value := range p.PackageChecksums {
|
||||||
digests = append(digests, file.Digest{Algorithm: string(value.Algorithm), Value: value.Value})
|
digests = append(digests, file.Digest{Algorithm: fromChecksumAlgorithm(value.Algorithm), Value: value.Value})
|
||||||
}
|
}
|
||||||
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
||||||
ArchiveDigests: digests,
|
ArchiveDigests: digests,
|
||||||
@ -392,7 +566,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
|||||||
case pkg.GoModulePkg:
|
case pkg.GoModulePkg:
|
||||||
var h1Digest string
|
var h1Digest string
|
||||||
for _, value := range p.PackageChecksums {
|
for _, value := range p.PackageChecksums {
|
||||||
digest, err := util.HDigestFromSHA(string(value.Algorithm), value.Value)
|
digest, err := util.HDigestFromSHA(fromChecksumAlgorithm(value.Algorithm), value.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("invalid h1digest: %v %v", value, err)
|
log.Debugf("invalid h1digest: %v %v", value, err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/spdx/tools-golang/spdx"
|
"github.com/spdx/tools-golang/spdx"
|
||||||
"github.com/spdx/tools-golang/spdx/v2/common"
|
"github.com/spdx/tools-golang/spdx/v2/common"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -12,6 +14,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -324,7 +327,7 @@ func TestH1Digest(t *testing.T) {
|
|||||||
|
|
||||||
func Test_toSyftRelationships(t *testing.T) {
|
func Test_toSyftRelationships(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
spdxIDMap map[string]interface{}
|
spdxIDMap map[string]any
|
||||||
doc *spdx.Document
|
doc *spdx.Document
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,9 +364,9 @@ func Test_toSyftRelationships(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "evident-by relationship",
|
name: "evident-by relationship",
|
||||||
args: args{
|
args: args{
|
||||||
spdxIDMap: map[string]interface{}{
|
spdxIDMap: map[string]any{
|
||||||
string(toSPDXID(pkg1)): &pkg1,
|
string(toSPDXID(pkg1)): pkg1,
|
||||||
string(toSPDXID(loc1)): &loc1,
|
string(toSPDXID(loc1)): loc1,
|
||||||
},
|
},
|
||||||
doc: &spdx.Document{
|
doc: &spdx.Document{
|
||||||
Relationships: []*spdx.Relationship{
|
Relationships: []*spdx.Relationship{
|
||||||
@ -391,9 +394,9 @@ func Test_toSyftRelationships(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "ownership-by-file-overlap relationship",
|
name: "ownership-by-file-overlap relationship",
|
||||||
args: args{
|
args: args{
|
||||||
spdxIDMap: map[string]interface{}{
|
spdxIDMap: map[string]any{
|
||||||
string(toSPDXID(pkg2)): &pkg2,
|
string(toSPDXID(pkg2)): pkg2,
|
||||||
string(toSPDXID(pkg3)): &pkg3,
|
string(toSPDXID(pkg3)): pkg3,
|
||||||
},
|
},
|
||||||
doc: &spdx.Document{
|
doc: &spdx.Document{
|
||||||
Relationships: []*spdx.Relationship{
|
Relationships: []*spdx.Relationship{
|
||||||
@ -431,3 +434,121 @@ func Test_toSyftRelationships(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_convertToAndFromFormat(t *testing.T) {
|
||||||
|
packages := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "pkg1",
|
||||||
|
MetadataType: pkg.UnknownMetadataType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pkg2",
|
||||||
|
MetadataType: pkg.UnknownMetadataType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range packages {
|
||||||
|
(&packages[i]).SetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
relationships := []artifact.Relationship{
|
||||||
|
{
|
||||||
|
From: packages[0],
|
||||||
|
To: packages[1],
|
||||||
|
Type: artifact.ContainsRelationship,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
source source.Description
|
||||||
|
packages []pkg.Package
|
||||||
|
relationships []artifact.Relationship
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "image source",
|
||||||
|
source: source.Description{
|
||||||
|
ID: "DocumentRoot-Image-some-image",
|
||||||
|
Metadata: source.StereoscopeImageSourceMetadata{
|
||||||
|
ID: "DocumentRoot-Image-some-image",
|
||||||
|
UserInput: "some-image:some-tag",
|
||||||
|
ManifestDigest: "sha256:ab8b83234bc28f28d8e",
|
||||||
|
},
|
||||||
|
Name: "some-image",
|
||||||
|
Version: "some-tag",
|
||||||
|
},
|
||||||
|
packages: packages,
|
||||||
|
relationships: relationships,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ". directory source",
|
||||||
|
source: source.Description{
|
||||||
|
ID: "DocumentRoot-Directory-.",
|
||||||
|
Name: ".",
|
||||||
|
Metadata: source.DirectorySourceMetadata{
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: packages,
|
||||||
|
relationships: relationships,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "directory source",
|
||||||
|
source: source.Description{
|
||||||
|
ID: "DocumentRoot-Directory-my-app",
|
||||||
|
Name: "my-app",
|
||||||
|
Metadata: source.DirectorySourceMetadata{
|
||||||
|
Path: "my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: packages,
|
||||||
|
relationships: relationships,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file source",
|
||||||
|
source: source.Description{
|
||||||
|
ID: "DocumentRoot-File-my-app.exe",
|
||||||
|
Metadata: source.FileSourceMetadata{
|
||||||
|
Path: "my-app.exe",
|
||||||
|
Digests: []file.Digest{
|
||||||
|
{
|
||||||
|
Algorithm: "sha256",
|
||||||
|
Value: "3723cae0b8b83234bc28f28d8e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "my-app.exe",
|
||||||
|
},
|
||||||
|
packages: packages,
|
||||||
|
relationships: relationships,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
src := &test.source
|
||||||
|
s := sbom.SBOM{
|
||||||
|
Source: *src,
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
Packages: pkg.NewCollection(test.packages...),
|
||||||
|
},
|
||||||
|
Relationships: test.relationships,
|
||||||
|
}
|
||||||
|
doc := ToFormatModel(s)
|
||||||
|
got, err := ToSyftModel(doc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if diff := cmp.Diff(&s, got,
|
||||||
|
cmpopts.IgnoreUnexported(artifact.Relationship{}),
|
||||||
|
cmpopts.IgnoreUnexported(file.LocationSet{}),
|
||||||
|
cmpopts.IgnoreUnexported(pkg.Collection{}),
|
||||||
|
cmpopts.IgnoreUnexported(pkg.Package{}),
|
||||||
|
cmpopts.IgnoreUnexported(pkg.LicenseSet{}),
|
||||||
|
cmpopts.IgnoreFields(pkg.Package{}, "MetadataType"),
|
||||||
|
cmpopts.IgnoreFields(sbom.Artifacts{}, "FileMetadata", "FileDigests"),
|
||||||
|
); diff != "" {
|
||||||
|
t.Fatalf("packages do not match:\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -92,7 +92,7 @@ func TestSPDXJSONDecoder(t *testing.T) {
|
|||||||
relationships:
|
relationships:
|
||||||
for _, pkgName := range test.relationships {
|
for _, pkgName := range test.relationships {
|
||||||
for _, rel := range sbom.Relationships {
|
for _, rel := range sbom.Relationships {
|
||||||
p, ok := rel.From.(*pkg.Package)
|
p, ok := rel.From.(pkg.Package)
|
||||||
if ok && p.Name == pkgName {
|
if ok && p.Name == pkgName {
|
||||||
continue relationships
|
continue relationships
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,12 +58,29 @@
|
|||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "some/path",
|
||||||
|
"SPDXID": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||||
|
"downloadLocation": "",
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"primaryPackagePurpose": "FILE"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relationships": [
|
"relationships": [
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
"relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||||
"relationshipType": "DESCRIBES"
|
"relationshipType": "DESCRIBES"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -58,12 +58,43 @@
|
|||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user-image-input",
|
||||||
|
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||||
|
"downloadLocation": "",
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"checksums": [
|
||||||
|
{
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"checksumValue": "2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
|
"referenceType": "purl",
|
||||||
|
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryPackagePurpose": "CONTAINER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relationships": [
|
"relationships": [
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
"relatedSpdxElement": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
"relationshipType": "DESCRIBES"
|
"relationshipType": "DESCRIBES"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -58,6 +58,27 @@
|
|||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user-image-input",
|
||||||
|
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||||
|
"downloadLocation": "",
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"checksums": [
|
||||||
|
{
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"checksumValue": "2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
|
"referenceType": "purl",
|
||||||
|
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryPackagePurpose": "CONTAINER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
@ -183,9 +204,19 @@
|
|||||||
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
||||||
"relationshipType": "CONTAINS"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
|
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||||
|
"relationshipType": "CONTAINS"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
"relatedSpdxElement": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||||
"relationshipType": "DESCRIBES"
|
"relationshipType": "DESCRIBES"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -61,7 +61,8 @@ func TestSPDXJSONSPDXIDs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Relationships: nil,
|
Relationships: nil,
|
||||||
Source: source.Description{
|
Source: source.Description{
|
||||||
Metadata: source.DirectorySourceMetadata{Path: "foobar/baz"}, // in this case, foobar is used as the spdx docment name
|
Name: "foobar/baz", // in this case, foobar is used as the spdx document name
|
||||||
|
Metadata: source.DirectorySourceMetadata{},
|
||||||
},
|
},
|
||||||
Descriptor: sbom.Descriptor{
|
Descriptor: sbom.Descriptor{
|
||||||
Name: "syft",
|
Name: "syft",
|
||||||
|
|||||||
@ -8,10 +8,17 @@ Creator: Organization: Anchore, Inc
|
|||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: redacted
|
Created: redacted
|
||||||
|
|
||||||
|
##### Package: foobar/baz
|
||||||
|
|
||||||
|
PackageName: foobar/baz
|
||||||
|
SPDXID: SPDXRef-DocumentRoot-Directory-foobar-baz
|
||||||
|
PrimaryPackagePurpose: FILE
|
||||||
|
FilesAnalyzed: false
|
||||||
|
|
||||||
##### Package: @at-sign
|
##### Package: @at-sign
|
||||||
|
|
||||||
PackageName: @at-sign
|
PackageName: @at-sign
|
||||||
SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
SPDXID: SPDXRef-Package--at-sign-3732f7a5679bdec4
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
PackageSourceInfo: acquired package info from the following paths:
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
@ -22,7 +29,7 @@ PackageCopyrightText: NOASSERTION
|
|||||||
##### Package: some/slashes
|
##### Package: some/slashes
|
||||||
|
|
||||||
PackageName: some/slashes
|
PackageName: some/slashes
|
||||||
SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
SPDXID: SPDXRef-Package-some-slashes-1345166d4801153b
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
PackageSourceInfo: acquired package info from the following paths:
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
@ -33,7 +40,7 @@ PackageCopyrightText: NOASSERTION
|
|||||||
##### Package: under_scores
|
##### Package: under_scores
|
||||||
|
|
||||||
PackageName: under_scores
|
PackageName: under_scores
|
||||||
SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
SPDXID: SPDXRef-Package-under-scores-290d5c77210978c1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
PackageSourceInfo: acquired package info from the following paths:
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
@ -43,5 +50,8 @@ PackageCopyrightText: NOASSERTION
|
|||||||
|
|
||||||
##### Relationships
|
##### Relationships
|
||||||
|
|
||||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-3732f7a5679bdec4
|
||||||
|
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-1345166d4801153b
|
||||||
|
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-290d5c77210978c1
|
||||||
|
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-foobar-baz
|
||||||
|
|
||||||
|
|||||||
@ -46,6 +46,16 @@ FileType: OTHER
|
|||||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||||
LicenseConcluded: NOASSERTION
|
LicenseConcluded: NOASSERTION
|
||||||
|
|
||||||
|
##### Package: user-image-input
|
||||||
|
|
||||||
|
PackageName: user-image-input
|
||||||
|
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
|
||||||
|
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||||
|
PrimaryPackagePurpose: CONTAINER
|
||||||
|
FilesAnalyzed: false
|
||||||
|
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||||
|
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||||
|
|
||||||
##### Package: package-2
|
##### Package: package-2
|
||||||
|
|
||||||
PackageName: package-2
|
PackageName: package-2
|
||||||
@ -82,5 +92,7 @@ Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef
|
|||||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
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-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
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-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,13 @@ Creator: Organization: Anchore, Inc
|
|||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: redacted
|
Created: redacted
|
||||||
|
|
||||||
|
##### Package: some/path
|
||||||
|
|
||||||
|
PackageName: some/path
|
||||||
|
SPDXID: SPDXRef-DocumentRoot-Directory-some-path
|
||||||
|
PrimaryPackagePurpose: FILE
|
||||||
|
FilesAnalyzed: false
|
||||||
|
|
||||||
##### Package: package-2
|
##### Package: package-2
|
||||||
|
|
||||||
PackageName: package-2
|
PackageName: package-2
|
||||||
@ -38,5 +45,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
|||||||
|
|
||||||
##### Relationships
|
##### Relationships
|
||||||
|
|
||||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
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-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,16 @@ Creator: Organization: Anchore, Inc
|
|||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: redacted
|
Created: redacted
|
||||||
|
|
||||||
|
##### Package: user-image-input
|
||||||
|
|
||||||
|
PackageName: user-image-input
|
||||||
|
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
|
||||||
|
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||||
|
PrimaryPackagePurpose: CONTAINER
|
||||||
|
FilesAnalyzed: false
|
||||||
|
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||||
|
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||||
|
|
||||||
##### Package: package-2
|
##### Package: package-2
|
||||||
|
|
||||||
PackageName: package-2
|
PackageName: package-2
|
||||||
@ -38,5 +48,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
|||||||
|
|
||||||
##### Relationships
|
##### Relationships
|
||||||
|
|
||||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
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-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/linux"
|
"github.com/anchore/syft/syft/linux"
|
||||||
@ -77,11 +78,18 @@ func (s SBOM) RelationshipsForPackage(p pkg.Package, rt ...artifact.Relationship
|
|||||||
|
|
||||||
var relationships []artifact.Relationship
|
var relationships []artifact.Relationship
|
||||||
for _, relationship := range s.Relationships {
|
for _, relationship := range s.Relationships {
|
||||||
// check if the relationship is one we're searching for; rt is inclusive
|
if relationship.From == nil || relationship.To == nil {
|
||||||
idx := slices.IndexFunc(rt, func(r artifact.RelationshipType) bool { return relationship.Type == r })
|
log.Debugf("relationship has nil edge, skipping: %#v", relationship)
|
||||||
if relationship.From.ID() == p.ID() && idx != -1 {
|
continue
|
||||||
relationships = append(relationships, relationship)
|
|
||||||
}
|
}
|
||||||
|
if relationship.From.ID() != p.ID() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if the relationship is one we're searching for; rt is inclusive
|
||||||
|
if !slices.ContainsFunc(rt, func(r artifact.RelationshipType) bool { return relationship.Type == r }) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
relationships = append(relationships, relationship)
|
||||||
}
|
}
|
||||||
|
|
||||||
return relationships
|
return relationships
|
||||||
|
|||||||
@ -120,6 +120,9 @@ func (s DirectorySource) Describe() Description {
|
|||||||
if a.Name != "" {
|
if a.Name != "" {
|
||||||
name = a.Name
|
name = a.Name
|
||||||
}
|
}
|
||||||
|
if a.Version != "" {
|
||||||
|
version = a.Version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Description{
|
return Description{
|
||||||
ID: string(s.id),
|
ID: string(s.id),
|
||||||
|
|||||||
@ -5,10 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bmatcuk/doublestar/v4"
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope"
|
"github.com/anchore/stereoscope"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||||
@ -83,18 +85,45 @@ func (s StereoscopeImageSource) ID() artifact.ID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s StereoscopeImageSource) Describe() Description {
|
func (s StereoscopeImageSource) Describe() Description {
|
||||||
name := s.metadata.UserInput
|
|
||||||
version := s.metadata.ManifestDigest
|
|
||||||
|
|
||||||
a := s.config.Alias
|
a := s.config.Alias
|
||||||
if a.Name != "" {
|
|
||||||
name = a.Name
|
name := a.Name
|
||||||
|
nameIfUnset := func(n string) {
|
||||||
|
if name != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = n
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Version != "" {
|
version := a.Version
|
||||||
version = a.Version
|
versionIfUnset := func(v string) {
|
||||||
|
if version != "" && version != "latest" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref, err := reference.Parse(s.metadata.UserInput)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("unable to parse image ref: %s", s.config.Reference)
|
||||||
|
} else {
|
||||||
|
if ref, ok := ref.(reference.Named); ok {
|
||||||
|
nameIfUnset(ref.Name())
|
||||||
|
} else {
|
||||||
|
nameIfUnset(s.metadata.UserInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref, ok := ref.(reference.NamedTagged); ok {
|
||||||
|
versionIfUnset(ref.Tag())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref, ok := ref.(reference.Digested); ok {
|
||||||
|
versionIfUnset(ref.Digest().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
versionIfUnset(s.metadata.ManifestDigest)
|
||||||
|
|
||||||
return Description{
|
return Description{
|
||||||
ID: string(s.id),
|
ID: string(s.id),
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user