mirror of
https://github.com/anchore/syft.git
synced 2026-10-23 11:32:21 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
248023baaf | ||
|
|
b7295b79de | ||
|
|
321eddf874 | ||
|
|
25bf679f8f | ||
|
|
02a8fb6f8c | ||
|
|
b46d044d7e | ||
|
|
15e45a8ce1 | ||
|
|
782b2e3348 | ||
|
|
e415bb21e7 | ||
|
|
748cfbf006 | ||
|
|
8bc5d84481 | ||
|
|
68b7ad9770 | ||
|
|
1aeda6bb50 |
@@ -21,6 +21,14 @@ fi
|
||||
if [ -z "$DOCKER_PASSWORD" ]; then
|
||||
exit_with_error "DOCKER_PASSWORD not set"
|
||||
fi
|
||||
|
||||
if [ -z "$GHCR_USERNAME" ]; then
|
||||
exit_with_error "GHCR_USERNAME not set"
|
||||
fi
|
||||
|
||||
if [ -z "$GHCR_PASSWORD" ]; then
|
||||
exit_with_error "GHCR_PASSWORD not set"
|
||||
fi
|
||||
set -u
|
||||
|
||||
# setup_signing
|
||||
@@ -41,4 +49,5 @@ setup_signing() {
|
||||
|
||||
commentary "log into docker -- required for publishing (since the default keychain has now been replaced)"
|
||||
echo "${DOCKER_PASSWORD}" | docker login docker.io -u "${DOCKER_USERNAME}" --password-stdin
|
||||
echo "${GHCR_PASSWORD}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ jobs:
|
||||
needs: [quality-gate]
|
||||
# due to our code signing process, it's vital that we run our release steps on macOS
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: docker-practice/actions-setup-docker@v1
|
||||
|
||||
@@ -128,6 +130,8 @@ jobs:
|
||||
- name: Build & publish release artifacts
|
||||
run: make release
|
||||
env:
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
GHCR_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
DOCKER_USERNAME: ${{ secrets.TOOLBOX_DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.TOOLBOX_DOCKER_PASS }}
|
||||
# we use a different token than GITHUB_SECRETS to additionally allow updating the homebrew repos
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: PR for latest Stereoscope release
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 8 * * *" # 3 AM EST
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
upgrade-stereoscope:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
|
||||
- run: |
|
||||
LATEST_VERSION=$(git ls-remote https://github.com/anchore/stereoscope main | head -n1 | awk '{print $1;}')
|
||||
|
||||
# update go.mod
|
||||
go get github.com/anchore/stereoscope@$LATEST_VERSION
|
||||
go mod tidy
|
||||
|
||||
# export the version for use with create-pull-request
|
||||
echo "::set-output name=LATEST_VERSION::$LATEST_VERSION"
|
||||
id: latest-version
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest
|
||||
labels: dependencies
|
||||
commit-message: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
title: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
body: |
|
||||
Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -99,6 +99,11 @@ dockers:
|
||||
- "anchore/syft:{{ .Tag }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
|
||||
- "ghcr.io/anchore/syft:latest"
|
||||
- "ghcr.io/anchore/syft:{{ .Tag }}-amd64"
|
||||
- "ghcr.io/anchore/syft:v{{ .Major }}-amd64"
|
||||
- "ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
@@ -112,6 +117,9 @@ dockers:
|
||||
- "anchore/syft:{{ .Tag }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
|
||||
- "ghcr.io/anchore/syft:{{ .Tag }}-arm64v8"
|
||||
- "ghcr.io/anchore/syft:v{{ .Major }}-arm64v8"
|
||||
- "ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
@@ -131,3 +139,11 @@ docker_manifests:
|
||||
image_templates:
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: ghcr.io/anchore/syft:{{ .Tag }}
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: ghcr.io/anchore/syft:latest
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
|
||||
@@ -156,9 +156,9 @@ syft <image> -o <format>
|
||||
Where the `formats` available are:
|
||||
- `json`: Use this to get as much information out of Syft as possible!
|
||||
- `text`: A row-oriented, human-and-machine-friendly output.
|
||||
- `cyclonedx`: A XML report conforming to the [CycloneDX 1.3 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.3 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `spdx`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
|
||||
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
|
||||
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
|
||||
- `table`: A columnar summary (default).
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b
|
||||
github.com/anchore/stereoscope v0.0.0-20220330165332-7fc73ee7b0f0
|
||||
github.com/anchore/stereoscope v0.0.0-20220406160859-c03a18a6b270
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
|
||||
@@ -280,8 +280,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b h1:YJWYt/6KQXR9JR46lLHrTTYi8rcye42tKcyjREA/hvA=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20220330165332-7fc73ee7b0f0 h1:mObz7bepZ6DtbIrsB2mxuOE9XEYmVtA/P/EDlKwfbjs=
|
||||
github.com/anchore/stereoscope v0.0.0-20220330165332-7fc73ee7b0f0/go.mod h1:yoCLUZY0k/pYLNIy0L80p2Ko0PKVNXm8rHtgxp4OiSc=
|
||||
github.com/anchore/stereoscope v0.0.0-20220406160859-c03a18a6b270 h1:NmxPDR6vo3xjwCL6o+tpF1vUad/BVo+WaVSwueB9W9w=
|
||||
github.com/anchore/stereoscope v0.0.0-20220406160859-c03a18a6b270/go.mod h1:yoCLUZY0k/pYLNIy0L80p2Ko0PKVNXm8rHtgxp4OiSc=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/formats/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -39,9 +40,23 @@ func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
Description: encodeDescription(p),
|
||||
ExternalReferences: encodeExternalReferences(p),
|
||||
Properties: properties,
|
||||
BOMRef: deriveBomRef(p),
|
||||
}
|
||||
}
|
||||
|
||||
func deriveBomRef(p pkg.Package) string {
|
||||
// try and parse the PURL if possible and append syft id to it, to make
|
||||
// the purl unique in the BOM.
|
||||
// TODO: In the future we may want to dedupe by PURL and combine components with
|
||||
// the same PURL while preserving their unique metadata.
|
||||
if parsedPURL, err := packageurl.FromString(p.PURL); err == nil {
|
||||
parsedPURL.Qualifiers = append(parsedPURL.Qualifiers, packageurl.Qualifier{Key: "syft-id", Value: string(p.ID())})
|
||||
return parsedPURL.ToString()
|
||||
}
|
||||
// fallback is to use strictly the ID if there is no valid pURL
|
||||
return string(p.ID())
|
||||
}
|
||||
|
||||
func hasMetadata(p pkg.Package) bool {
|
||||
return p.Metadata != nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
@@ -139,3 +140,54 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_deriveBomRef(t *testing.T) {
|
||||
pkgWithPurl := pkg.Package{
|
||||
Name: "django",
|
||||
Version: "1.11.1",
|
||||
PURL: "pkg:pypi/django@1.11.1",
|
||||
}
|
||||
pkgWithPurl.SetID()
|
||||
|
||||
pkgWithOutPurl := pkg.Package{
|
||||
Name: "django",
|
||||
Version: "1.11.1",
|
||||
PURL: "",
|
||||
}
|
||||
pkgWithOutPurl.SetID()
|
||||
|
||||
pkgWithBadPurl := pkg.Package{
|
||||
Name: "django",
|
||||
Version: "1.11.1",
|
||||
PURL: "pkg:pyjango@1.11.1",
|
||||
}
|
||||
pkgWithBadPurl.SetID()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "use pURL-id hybrid",
|
||||
pkg: pkgWithPurl,
|
||||
want: fmt.Sprintf("pkg:pypi/django@1.11.1?syft-id=%s", pkgWithPurl.ID()),
|
||||
},
|
||||
{
|
||||
name: "fallback to ID when pURL is invalid",
|
||||
pkg: pkgWithBadPurl,
|
||||
want: string(pkgWithBadPurl.ID()),
|
||||
},
|
||||
{
|
||||
name: "fallback to ID when pURL is missing",
|
||||
pkg: pkgWithOutPurl,
|
||||
want: string(pkgWithOutPurl.ID()),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.pkg.ID()
|
||||
assert.Equal(t, tt.want, deriveBomRef(tt.pkg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
syftFile "github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
//nolint:funlen, gocognit
|
||||
func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
refs := []cyclonedx.ExternalReference{}
|
||||
var refs []cyclonedx.ExternalReference
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
@@ -46,6 +49,19 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
Type: cyclonedx.ERTypeWebsite,
|
||||
})
|
||||
}
|
||||
case pkg.JavaMetadata:
|
||||
if len(metadata.ArchiveDigests) > 0 {
|
||||
for _, digest := range metadata.ArchiveDigests {
|
||||
refs = append(refs, cyclonedx.ExternalReference{
|
||||
URL: "",
|
||||
Type: cyclonedx.ERTypeBuildMeta,
|
||||
Hashes: &[]cyclonedx.Hash{{
|
||||
Algorithm: cyclonedx.HashAlgorithm(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
case pkg.PythonPackageMetadata:
|
||||
if metadata.DirectURLOrigin != nil && metadata.DirectURLOrigin.URL != "" {
|
||||
ref := cyclonedx.ExternalReference{
|
||||
@@ -79,6 +95,20 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||
case *pkg.GemMetadata:
|
||||
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||
case *pkg.JavaMetadata:
|
||||
var digests []syftFile.Digest
|
||||
if ref := findExternalRef(c, cyclonedx.ERTypeBuildMeta); ref != nil {
|
||||
if ref.Hashes != nil {
|
||||
for _, hash := range *ref.Hashes {
|
||||
digests = append(digests, syftFile.Digest{
|
||||
Algorithm: string(hash.Algorithm),
|
||||
Value: hash.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
meta.ArchiveDigests = digests
|
||||
case *pkg.PythonPackageMetadata:
|
||||
if meta.DirectURLOrigin == nil {
|
||||
meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{}
|
||||
|
||||
@@ -22,5 +22,6 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
|
||||
ReferenceType: PurlExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var expr = regexp.MustCompile("[^a-zA-Z0-9.-]")
|
||||
|
||||
// SPDX spec says SPDXID must be:
|
||||
// "SPDXRef-"[idstring] where [idstring] is a unique string containing letters, numbers, ., and/or -
|
||||
func SanitizeElementID(id string) string {
|
||||
return expr.ReplaceAllString(id, "-")
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_SanitizeElementID(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "letters",
|
||||
expected: "letters",
|
||||
},
|
||||
{
|
||||
input: "ssl-client",
|
||||
expected: "ssl-client",
|
||||
},
|
||||
{
|
||||
input: "ssl_client",
|
||||
expected: "ssl-client",
|
||||
},
|
||||
{
|
||||
input: "go-module-sigs.k8s.io/structured-merge-diff/v3",
|
||||
expected: "go-module-sigs.k8s.io-structured-merge-diff-v3",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
actual := SanitizeElementID(test.input)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -309,6 +309,14 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
|
||||
Architecture: arch,
|
||||
Maintainer: p.PackageOriginatorPerson,
|
||||
}
|
||||
case pkg.JavaPkg:
|
||||
var digests []file.Digest
|
||||
for algorithm, value := range p.PackageChecksums {
|
||||
digests = append(digests, file.Digest{Algorithm: string(algorithm), Value: value.Value})
|
||||
}
|
||||
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
||||
ArchiveDigests: digests,
|
||||
}
|
||||
}
|
||||
return pkg.UnknownMetadataType, nil
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
@@ -187,7 +187,7 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
@@ -249,7 +249,7 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
},
|
||||
},
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
@@ -267,7 +267,7 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
|
||||
+5
-3
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:498e659b-0758-4a7f-816e-91bee18df634",
|
||||
"serialNumber": "urn:uuid:dec3f6b4-8458-48bb-b60d-dfd312f6ec4e",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-03-08T12:30:39Z",
|
||||
"timestamp": "2022-04-01T11:48:04-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -20,6 +20,7 @@
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "b85dbb4e6ece5082",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@@ -56,11 +57,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?syft-id=ceda99598967ae8d",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"purl": "a-purl-2",
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:package:foundBy",
|
||||
|
||||
+8
-6
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:342c3d2c-d26e-47b6-94d6-92fbf41da945",
|
||||
"serialNumber": "urn:uuid:054d973e-fe99-4762-92e4-eaf01997ae41",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-03-08T12:30:39Z",
|
||||
"timestamp": "2022-04-01T11:48:04-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "711095b1cdf90cce",
|
||||
"bom-ref": "e777314b02b362e4",
|
||||
"type": "container",
|
||||
"name": "user-image-input",
|
||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
@@ -21,6 +21,7 @@
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "2a46171f91c8d4bc",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@@ -52,7 +53,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab"
|
||||
"value": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
@@ -61,11 +62,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?syft-id=ae77680e9b1d087e",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"purl": "a-purl-2",
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:package:foundBy",
|
||||
@@ -81,7 +83,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67"
|
||||
"value": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
||||
BIN
Binary file not shown.
+5
-5
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:892f8304-0142-45b1-b411-cade3c53057f" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:554fd820-210b-40c8-8c0b-75690274e21c" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-03-08T12:30:33Z</timestamp>
|
||||
<timestamp>2022-04-01T11:57:46-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -14,7 +14,7 @@
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component type="library">
|
||||
<component bom-ref="b85dbb4e6ece5082" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
@@ -32,11 +32,11 @@
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?syft-id=ceda99598967ae8d" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-2</purl>
|
||||
<purl>pkg:deb/debian/package-2@2.0.1</purl>
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:5fa94827-eb85-4f32-a62d-76fb6e89a2dd" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:1535f940-172f-4d97-8280-d5a5764d1557" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-03-08T12:30:33Z</timestamp>
|
||||
<timestamp>2022-04-01T11:57:46-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -9,13 +9,13 @@
|
||||
<version>[not provided]</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="711095b1cdf90cce" type="container">
|
||||
<component bom-ref="e777314b02b362e4" type="container">
|
||||
<name>user-image-input</name>
|
||||
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component type="library">
|
||||
<component bom-ref="2a46171f91c8d4bc" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
@@ -30,20 +30,20 @@
|
||||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:layerID">sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab</property>
|
||||
<property name="syft:location:0:layerID">sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?syft-id=ae77680e9b1d087e" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-2</purl>
|
||||
<purl>pkg:deb/debian/package-2@2.0.1</purl>
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:layerID">sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67</property>
|
||||
<property name="syft:location:0:layerID">sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
|
||||
BIN
Binary file not shown.
@@ -130,7 +130,7 @@ func toGithubManifests(s *sbom.SBOM) Manifests {
|
||||
|
||||
name := dependencyName(p)
|
||||
manifest.Resolved[name] = DependencyNode{
|
||||
Purl: p.PURL,
|
||||
PackageURL: p.PURL,
|
||||
Metadata: toDependencyMetadata(p),
|
||||
Relationship: toDependencyRelationshipType(p),
|
||||
Scope: toDependencyScope(p),
|
||||
|
||||
@@ -104,12 +104,12 @@ func Test_toGithubModel(t *testing.T) {
|
||||
},
|
||||
Resolved: DependencyGraph{
|
||||
"pkg:generic/pkg-1@1.0.1": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-1@1.0.1",
|
||||
PackageURL: "pkg:generic/pkg-1@1.0.1",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
"pkg:generic/pkg-2@2.0.2": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-2@2.0.2",
|
||||
PackageURL: "pkg:generic/pkg-2@2.0.2",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
@@ -125,7 +125,7 @@ func Test_toGithubModel(t *testing.T) {
|
||||
},
|
||||
Resolved: DependencyGraph{
|
||||
"pkg:generic/pkg-3@3.0.3": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-3@3.0.3",
|
||||
PackageURL: "pkg:generic/pkg-3@3.0.3",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
|
||||
@@ -14,9 +14,9 @@ type DependencySnapshot struct {
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
Name string `json:"name,omitempty"` // !omitempty
|
||||
ID string `json:"id,omitempty"` // !omitempty
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Correlator string `json:"correlator,omitempty"` // !omitempty
|
||||
ID string `json:"id,omitempty"` // !omitempty
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
}
|
||||
|
||||
type DetectorMetadata struct {
|
||||
@@ -62,7 +62,7 @@ const (
|
||||
)
|
||||
|
||||
type DependencyNode struct {
|
||||
Purl string `json:"purl,omitempty"`
|
||||
PackageURL string `json:"package_url,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Relationship DependencyRelationship `json:"relationship,omitempty"`
|
||||
Scope DependencyScope `json:"scope,omitempty"`
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
|
||||
// ElementID represents the identifier string portion of an SPDX element
|
||||
// identifier. DocElementID should be used for any attributes which can
|
||||
// contain identifiers defined in a different SPDX document.
|
||||
@@ -7,31 +9,5 @@ package model
|
||||
type ElementID string
|
||||
|
||||
func (e ElementID) String() string {
|
||||
return "SPDXRef-" + string(e)
|
||||
}
|
||||
|
||||
// DocElementID represents an SPDX element identifier that could be defined
|
||||
// in a different SPDX document, and therefore could have a "DocumentRef-"
|
||||
// portion, such as Relationship and Annotations.
|
||||
// ElementID is used for attributes in which a "DocumentRef-" portion cannot
|
||||
// appear, such as a Package or File definition (since it is necessarily
|
||||
// being defined in the present document).
|
||||
// DocumentRefID will be the empty string for elements defined in the
|
||||
// present document.
|
||||
// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
|
||||
// 'SPDXRef-' portions.
|
||||
type DocElementID struct {
|
||||
DocumentRefID string
|
||||
ElementRefID ElementID
|
||||
}
|
||||
|
||||
// RenderDocElementID takes a DocElementID and returns the string equivalent,
|
||||
// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
|
||||
// reinserted.
|
||||
func (d DocElementID) String() string {
|
||||
prefix := ""
|
||||
if d.DocumentRefID != "" {
|
||||
prefix = "DocumentRef-" + d.DocumentRefID + ":"
|
||||
}
|
||||
return prefix + d.ElementRefID.String()
|
||||
return "SPDXRef-" + spdxhelpers.SanitizeElementID(string(e))
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-03-30T21:48:28.297464Z",
|
||||
"created": "2022-04-01T15:48:39.459232Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
@@ -11,7 +11,7 @@
|
||||
"licenseListVersion": "3.16"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-e188d59b-76f6-4c7f-a9f2-1ae7d0577781",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-8d335d81-29c9-4236-84f1-2292ea92aaf5",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-b85dbb4e6ece5082",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-03-30T21:48:28.303986Z",
|
||||
"created": "2022-04-01T15:48:39.465643Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
@@ -11,7 +11,7 @@
|
||||
"licenseListVersion": "3.16"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-9e4f4190-c5ae-4e31-a852-d1ab71357516",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-e64e0be8-5031-4eec-842d-e59fb6deb518",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-2a46171f91c8d4bc",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -55,14 +55,32 @@ func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []m
|
||||
for _, p := range catalog.Sorted() {
|
||||
license := spdxhelpers.License(p)
|
||||
packageSpdxID := model.ElementID(p.ID()).String()
|
||||
filesAnalyzed := false
|
||||
|
||||
// we generate digest for some Java packages
|
||||
// see page 33 of the spdx specification for 2.2
|
||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
||||
var checksums []model.Checksum
|
||||
if p.MetadataType == pkg.JavaMetadataType {
|
||||
javaMetadata := p.Metadata.(pkg.JavaMetadata)
|
||||
if len(javaMetadata.ArchiveDigests) > 0 {
|
||||
filesAnalyzed = true
|
||||
for _, digest := range javaMetadata.ArchiveDigests {
|
||||
checksums = append(checksums, model.Checksum{
|
||||
Algorithm: strings.ToUpper(digest.Algorithm),
|
||||
ChecksumValue: digest.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// note: the license concluded and declared should be the same since we are collecting license information
|
||||
// from the project data itself (the installed package files).
|
||||
packages = append(packages, model.Package{
|
||||
Checksums: checksums,
|
||||
Description: spdxhelpers.Description(p),
|
||||
DownloadLocation: spdxhelpers.DownloadLocation(p),
|
||||
ExternalRefs: spdxhelpers.ExternalRefs(p),
|
||||
FilesAnalyzed: false,
|
||||
FilesAnalyzed: filesAnalyzed,
|
||||
HasFiles: fileIDsForPackage(packageSpdxID, relationships),
|
||||
Homepage: spdxhelpers.Homepage(p),
|
||||
// The Declared License is what the authors of a project believe govern the package
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var updateSpdxTagValue = flag.Bool("update-spdx-tv", false, "update the *.golden files for spdx-tv encoders")
|
||||
@@ -31,6 +34,32 @@ func TestSPDXTagValueImageEncoder(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestSPDXJSONSPDXIDs(t *testing.T) {
|
||||
var pkgs []pkg.Package
|
||||
for _, name := range []string{"some/slashes", "@at-sign", "under_scores"} {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
}
|
||||
p.SetID()
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
testutils.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
Format(),
|
||||
sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: pkg.NewCatalog(pkgs...),
|
||||
},
|
||||
Relationships: nil,
|
||||
Source: source.Metadata{
|
||||
Scheme: source.DirectoryScheme,
|
||||
},
|
||||
Descriptor: sbom.Descriptor{},
|
||||
},
|
||||
true,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func spdxTagValueRedactor(s []byte) []byte {
|
||||
// each SBOM reports the time it was generated, which is not useful during snapshot testing
|
||||
s = regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: .
|
||||
DocumentNamespace: https://anchore.com/syft/dir/e69056a9-935e-4f00-b85f-9467f5d99a92
|
||||
LicenseListVersion: 3.16
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-04-13T16:38:03Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
PackageName: @at-sign
|
||||
SPDXID: SPDXRef-Package---at-sign-739e4f0d93fb8298
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: some/slashes
|
||||
|
||||
PackageName: some/slashes
|
||||
SPDXID: SPDXRef-Package--some-slashes-26db06648b24bff9
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: under_scores
|
||||
|
||||
PackageName: under_scores
|
||||
SPDXID: SPDXRef-Package--under-scores-250cbfefcdea318b
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
+3
-3
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-71aa3553-1a73-405f-9f1f-6347d6d4593b
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-d227b0f2-4ee8-4e10-ac43-019db86d16ff
|
||||
LicenseListVersion: 3.16
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-03-30T21:48:22Z
|
||||
Created: 2022-04-01T15:48:44Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -19,7 +19,7 @@ PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
|
||||
##### Package: package-1
|
||||
|
||||
|
||||
+3
-3
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-e46e20f4-43a4-40e7-9f82-fd55b8a89e5f
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-49f98c61-3418-4427-9e00-8b1c735e9799
|
||||
LicenseListVersion: 3.16
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2022-03-30T21:48:22Z
|
||||
Created: 2022-04-01T15:48:44Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -19,7 +19,7 @@ PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
|
||||
##### Package: package-1
|
||||
|
||||
|
||||
@@ -96,13 +96,31 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
// name should be guaranteed to be unique, but semantically useful and stable
|
||||
id := fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID())
|
||||
id := spdxhelpers.SanitizeElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID()))
|
||||
|
||||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 3.16) is preferred.
|
||||
license := spdxhelpers.License(p)
|
||||
|
||||
filesAnalyzed := false
|
||||
checksums := make(map[spdx.ChecksumAlgorithm]spdx.Checksum)
|
||||
|
||||
// If the pkg type is Java we have attempted to generated a digest
|
||||
// FilesAnalyzed should be true in this case
|
||||
if p.MetadataType == pkg.JavaMetadataType {
|
||||
javaMetadata := p.Metadata.(pkg.JavaMetadata)
|
||||
if len(javaMetadata.ArchiveDigests) > 0 {
|
||||
filesAnalyzed = true
|
||||
for _, digest := range javaMetadata.ArchiveDigests {
|
||||
checksums[spdx.ChecksumAlgorithm(digest.Algorithm)] = spdx.Checksum{
|
||||
Algorithm: spdx.ChecksumAlgorithm(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
||||
|
||||
// NOT PART OF SPEC
|
||||
@@ -159,7 +177,7 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
|
||||
|
||||
// Intent: A package can refer to a project, product, artifact, distribution or a component that is
|
||||
// external to the SPDX document.
|
||||
FilesAnalyzed: false,
|
||||
FilesAnalyzed: filesAnalyzed,
|
||||
// NOT PART OF SPEC: did FilesAnalyzed tag appear?
|
||||
IsFilesAnalyzedTagPresent: true,
|
||||
|
||||
@@ -180,6 +198,7 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
|
||||
// to determine if any file in the original package has been changed. If the SPDX file is to be included
|
||||
// in a package, this value should not be calculated. The SHA-1 algorithm will be used to provide the
|
||||
// checksum by default.
|
||||
PackageChecksums: checksums,
|
||||
|
||||
// note: based on the purpose above no discovered checksums should be provided, but instead, only
|
||||
// tool-derived checksums.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
|
||||
@@ -77,30 +77,39 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
|
||||
digests, err := DigestsFromFile(contentReader, i.hashes)
|
||||
if err != nil {
|
||||
return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err}
|
||||
}
|
||||
|
||||
return digests, nil
|
||||
}
|
||||
|
||||
func DigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) {
|
||||
// create a set of hasher objects tied together with a single writer to feed content into
|
||||
hashers := make([]hash.Hash, len(i.hashes))
|
||||
writers := make([]io.Writer, len(i.hashes))
|
||||
for idx, hashObj := range i.hashes {
|
||||
hashers := make([]hash.Hash, len(hashes))
|
||||
writers := make([]io.Writer, len(hashes))
|
||||
for idx, hashObj := range hashes {
|
||||
hashers[idx] = hashObj.New()
|
||||
writers[idx] = hashers[idx]
|
||||
}
|
||||
|
||||
size, err := io.Copy(io.MultiWriter(writers...), contentReader)
|
||||
size, err := io.Copy(io.MultiWriter(writers...), closer)
|
||||
if err != nil {
|
||||
return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return make([]Digest, 0), nil
|
||||
}
|
||||
|
||||
result := make([]Digest, len(i.hashes))
|
||||
result := make([]Digest, len(hashes))
|
||||
// only capture digests when there is content. It is important to do this based on SIZE and not
|
||||
// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
|
||||
// file type but a body is still allowed.
|
||||
for idx, hasher := range hashers {
|
||||
result[idx] = Digest{
|
||||
Algorithm: DigestAlgorithmName(i.hashes[idx]),
|
||||
Algorithm: DigestAlgorithmName(hashes[idx]),
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ var defaultCandidateAdditions = buildCandidateLookup(
|
||||
{
|
||||
pkg.JavaPkg,
|
||||
candidateKey{PkgName: "springframework"},
|
||||
candidateAddition{AdditionalProducts: []string{"spring_framework", "springsource_spring_framework"}},
|
||||
candidateAddition{AdditionalProducts: []string{"spring_framework", "springsource_spring_framework"}, AdditionalVendors: []string{"pivotal_software", "springsource", "vmware"}},
|
||||
},
|
||||
{
|
||||
pkg.JavaPkg,
|
||||
candidateKey{PkgName: "spring-core"},
|
||||
candidateAddition{AdditionalProducts: []string{"spring_framework", "springsource_spring_framework"}},
|
||||
candidateAddition{AdditionalProducts: []string{"spring_framework", "springsource_spring_framework"}, AdditionalVendors: []string{"pivotal_software", "springsource", "vmware"}},
|
||||
},
|
||||
{
|
||||
// example image: docker.io/nuxeo:latest
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
syftFile "github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -34,6 +37,11 @@ var archiveFormatGlobs = []string{
|
||||
// project that we can build in CI feel free to include it
|
||||
}
|
||||
|
||||
// javaArchiveHashes are all the current hash algorithms used to calculate archive digests
|
||||
var javaArchiveHashes = []crypto.Hash{
|
||||
crypto.SHA1,
|
||||
}
|
||||
|
||||
type archiveParser struct {
|
||||
fileManifest file.ZipFileManifest
|
||||
virtualPath string
|
||||
@@ -101,6 +109,7 @@ func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error)
|
||||
}
|
||||
|
||||
// find aux packages from pom.properties/pom.xml and potentially modify the existing parentPkg
|
||||
// NOTE: we cannot generate sha1 digests from packages discovered via pom.properties/pom.xml
|
||||
auxPkgs, err := j.discoverPkgsFromAllMavenFiles(parentPkg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -135,6 +144,7 @@ func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error)
|
||||
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
|
||||
func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
// search and parse java manifest files
|
||||
// TODO: do we want to prefer or check for pom files over manifest here?
|
||||
manifestMatches := j.fileManifest.GlobMatch(manifestGlob)
|
||||
if len(manifestMatches) > 1 {
|
||||
return nil, fmt.Errorf("found multiple manifests in the jar: %+v", manifestMatches)
|
||||
@@ -157,6 +167,18 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
archiveCloser, err := os.Open(j.archivePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open archive path (%s): %w", j.archivePath, err)
|
||||
}
|
||||
defer archiveCloser.Close()
|
||||
|
||||
// grab and assign digest for the entire archive
|
||||
digests, err := syftFile.DigestsFromFile(archiveCloser, javaArchiveHashes)
|
||||
if err != nil {
|
||||
log.Warnf("failed to create digest for file=%q: %+v", j.archivePath, err)
|
||||
}
|
||||
|
||||
return &pkg.Package{
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
@@ -164,8 +186,9 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
Type: j.fileInfo.pkgType(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: j.virtualPath,
|
||||
Manifest: manifest,
|
||||
VirtualPath: j.virtualPath,
|
||||
Manifest: manifest,
|
||||
ArchiveDigests: digests,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -181,12 +204,14 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([
|
||||
|
||||
var pkgs []*pkg.Package
|
||||
|
||||
properties, err := pomPropertiesByParentPath(j.archivePath, j.fileManifest.GlobMatch(pomPropertiesGlob), j.virtualPath)
|
||||
// pom.properties
|
||||
properties, err := pomPropertiesByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomPropertiesGlob))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projects, err := pomProjectByParentPath(j.archivePath, j.fileManifest.GlobMatch(pomXMLGlob), j.virtualPath)
|
||||
// pom.xml
|
||||
projects, err := pomProjectByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomXMLGlob))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -273,7 +298,7 @@ func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener
|
||||
return nestedPkgs, nestedRelationships, nil
|
||||
}
|
||||
|
||||
func pomPropertiesByParentPath(archivePath string, extractPaths []string, virtualPath string) (map[string]pkg.PomProperties, error) {
|
||||
func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProperties, error) {
|
||||
contentsOfMavenPropertiesFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract maven files: %w", err)
|
||||
@@ -298,10 +323,11 @@ func pomPropertiesByParentPath(archivePath string, extractPaths []string, virtua
|
||||
|
||||
propertiesByParentPath[path.Dir(filePath)] = *pomProperties
|
||||
}
|
||||
|
||||
return propertiesByParentPath, nil
|
||||
}
|
||||
|
||||
func pomProjectByParentPath(archivePath string, extractPaths []string, virtualPath string) (map[string]pkg.PomProject, error) {
|
||||
func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProject, error) {
|
||||
contentsOfMavenProjectFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract maven files: %w", err)
|
||||
|
||||
@@ -283,6 +283,11 @@ func TestParseJar(t *testing.T) {
|
||||
metadata := a.Metadata.(pkg.JavaMetadata)
|
||||
metadata.Parent = nil
|
||||
|
||||
// redact Digest which is computed differently between CI and local
|
||||
if len(metadata.ArchiveDigests) > 0 {
|
||||
metadata.ArchiveDigests = nil
|
||||
}
|
||||
|
||||
// ignore select fields (only works for the main section)
|
||||
for _, field := range test.ignoreExtras {
|
||||
if metadata.Manifest != nil && metadata.Manifest.Main != nil {
|
||||
@@ -567,7 +572,6 @@ func TestParseNestedJar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
func saveArchiveToTmp(archiveVirtualPath string, reader io.Reader) (string, string, func(), error) {
|
||||
name := path.Base(archiveVirtualPath)
|
||||
name := filepath.Base(archiveVirtualPath)
|
||||
tempDir, err := ioutil.TempDir("", "syft-archive-contents-")
|
||||
if err != nil {
|
||||
return "", "", func() {}, fmt.Errorf("unable to create tempdir for archive processing: %w", err)
|
||||
|
||||
@@ -3,6 +3,7 @@ package pkg
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -20,12 +21,13 @@ var jenkinsPluginPomPropertiesGroupIDs = []string{
|
||||
|
||||
// JavaMetadata encapsulates all Java ecosystem metadata for a package as well as an (optional) parent relationship.
|
||||
type JavaMetadata struct {
|
||||
VirtualPath string `json:"virtualPath" cyclonedx:"virtualPath"` // we need to include the virtual path in cyclonedx documents to prevent deduplication of jars within jars
|
||||
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"`
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"`
|
||||
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
|
||||
PURL string `hash:"ignore" json:"-"` // pURLs and CPEs are ignored for package IDs
|
||||
Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy).
|
||||
VirtualPath string `json:"virtualPath" cyclonedx:"virtualPath"` // we need to include the virtual path in cyclonedx documents to prevent deduplication of jars within jars
|
||||
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"`
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"`
|
||||
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
|
||||
ArchiveDigests []file.Digest `hash:"ignore" json:"digest,omitempty"`
|
||||
PURL string `hash:"ignore" json:"-"` // pURLs and CPEs are ignored for package IDs
|
||||
Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy).
|
||||
}
|
||||
|
||||
// PomProperties represents the fields of interest extracted from a Java archive's pom.properties file.
|
||||
|
||||
Reference in New Issue
Block a user