mirror of
https://github.com/anchore/syft.git
synced 2026-10-14 07:07:18 +02:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52d2e62cdc | ||
|
|
51c6eb30f5 | ||
|
|
e1e9ccb401 | ||
|
|
220f3a24fd | ||
|
|
ca032434b3 | ||
|
|
5ab872c732 | ||
|
|
a04fa68539 | ||
|
|
76f8205936 | ||
|
|
ca081ae5e0 | ||
|
|
8f96adacfb | ||
|
|
8f292958a6 | ||
|
|
23e1bd3984 | ||
|
|
ac024f94a4 | ||
|
|
0ed2842123 | ||
|
|
b78c90d018 | ||
|
|
2c62651c82 | ||
|
|
5519a25035 | ||
|
|
950ad681a0 | ||
|
|
1e338502ff | ||
|
|
37251f6179 |
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ue
|
||||
|
||||
set +xu
|
||||
if [ -z "$AC_USERNAME" ]; then
|
||||
@@ -11,58 +10,33 @@ if [ -z "$AC_PASSWORD" ]; then
|
||||
fi
|
||||
set -u
|
||||
|
||||
# repackage [archive-path]
|
||||
#
|
||||
# returns an archive compatible for Apple's notarization process, repackaging the input archive as needed
|
||||
#
|
||||
repackage() {
|
||||
archive=$1
|
||||
|
||||
case "$archive" in
|
||||
*.tar.gz)
|
||||
new_archive=${archive%.tar.gz}.zip
|
||||
(
|
||||
tmp_dir=$(mktemp -d)
|
||||
cd "$tmp_dir"
|
||||
# redirect stdout to stderr to preserve the return value
|
||||
tar xzf "$archive" && zip "$new_archive" ./* 1>&2
|
||||
rm -rf "$tmp_dir"
|
||||
)
|
||||
echo "$new_archive"
|
||||
;;
|
||||
*.zip)
|
||||
echo "$archive"
|
||||
;;
|
||||
*) return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# notarize [archive-path]
|
||||
#
|
||||
notarize() {
|
||||
archive_path=$1
|
||||
binary_path=$1
|
||||
archive_path=${binary_path}-archive-for-notarization.zip
|
||||
|
||||
title "notarizing binaries found in the release archive"
|
||||
title "archiving release binary into ${archive_path}"
|
||||
|
||||
payload_archive_path=$(repackage "$archive_path")
|
||||
if [ "$?" != "0" ]; then
|
||||
exit_with_error "cannot prepare payload for notarization: $archive_path"
|
||||
fi
|
||||
parent=$(dirname "$binary_path")
|
||||
(
|
||||
cd "${parent}" && zip "${archive_path}" "$(basename ${binary_path})"
|
||||
)
|
||||
|
||||
if [ ! -f "$payload_archive_path" ]; then
|
||||
exit_with_error "cannot find payload for notarization: $payload_archive_path"
|
||||
if [ ! -f "$archive_path" ]; then
|
||||
exit_with_error "cannot find payload for notarization: $archive_path"
|
||||
fi
|
||||
|
||||
# install gon
|
||||
which gon || (brew tap mitchellh/gon && brew install mitchellh/gon/gon)
|
||||
|
||||
# create config (note: json via stdin with gon is broken, can only use HCL from file)
|
||||
tmp_file=$(mktemp).hcl
|
||||
hcl_file=$(mktemp).hcl
|
||||
|
||||
cat <<EOF > "$tmp_file"
|
||||
cat <<EOF > "$hcl_file"
|
||||
notarize {
|
||||
path = "$payload_archive_path"
|
||||
path = "$archive_path"
|
||||
bundle_id = "com.anchore.toolbox.syft"
|
||||
}
|
||||
|
||||
@@ -72,14 +46,8 @@ apple_id {
|
||||
}
|
||||
EOF
|
||||
|
||||
gon -log-level info "$tmp_file"
|
||||
gon -log-level info "$hcl_file"
|
||||
|
||||
result="$?"
|
||||
|
||||
rm "$tmp_file"
|
||||
|
||||
if [ "$result" -ne "0" ]; then
|
||||
exit_with_error "notarization failed"
|
||||
fi
|
||||
rm "${hcl_file}" "${archive_path}"
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ IS_SNAPSHOT="$1"
|
||||
## grab utilities
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
. "$SCRIPT_DIR"/utils.sh
|
||||
mkdir -p "$SCRIPT_DIR/log"
|
||||
|
||||
main() {
|
||||
|
||||
@@ -33,17 +34,13 @@ main() {
|
||||
echo -n "$MAC_SIGNING_IDENTITY" > "$SCRIPT_DIR/$SIGNING_IDENTITY_FILENAME"
|
||||
}
|
||||
|
||||
set +u
|
||||
if [ -z "$SCRIPT" ]
|
||||
then
|
||||
# capture all output from a subshell to log output additionally to a file (as well as the terminal)
|
||||
( (
|
||||
set +u
|
||||
if [ -n "$SKIP_SIGNING" ]; then
|
||||
commentary "skipping signing setup..."
|
||||
else
|
||||
set -u
|
||||
# log all output
|
||||
mkdir -p "$SCRIPT_DIR/log"
|
||||
/usr/bin/script "$SCRIPT_DIR/log/setup.txt" /bin/bash -c "$0 $*"
|
||||
exit $?
|
||||
elif [ -n "$SKIP_SIGNING" ]; then
|
||||
commentary "skipping signing setup..."
|
||||
else
|
||||
set -u
|
||||
main
|
||||
fi
|
||||
main
|
||||
fi
|
||||
) 2>&1) | tee "$SCRIPT_DIR/log/setup.txt"
|
||||
@@ -1,12 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
set -eu -o pipefail
|
||||
|
||||
ARCHIVE_PATH="$1"
|
||||
BINARY_PATH="$1"
|
||||
IS_SNAPSHOT="$2"
|
||||
TARGET_NAME="$3"
|
||||
|
||||
## grab utilities
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
. "$SCRIPT_DIR"/utils.sh
|
||||
mkdir -p "$SCRIPT_DIR/log"
|
||||
|
||||
|
||||
# sign_binary [binary-path] [signing-identity]
|
||||
@@ -38,58 +40,14 @@ sign_binary() {
|
||||
fi
|
||||
|
||||
codesign --verify "$exe_path" --verbose=4
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit_with_error "signing verification failed"
|
||||
fi
|
||||
}
|
||||
|
||||
# sign_binaries_in_archive [archive-abs-path] [signing-identity]
|
||||
#
|
||||
# signs all binaries within an archive (there must be at least one)
|
||||
#
|
||||
sign_binaries_in_archive() {
|
||||
archive_abs_path=$1
|
||||
identity=$2
|
||||
|
||||
scratch_path=$(mktemp -d)
|
||||
trap "rm -rf -- $scratch_path" EXIT
|
||||
|
||||
title "getting contents from the release archive: $archive_abs_path"
|
||||
tar -C "$scratch_path" -xvf "$archive_abs_path"
|
||||
|
||||
# invalidate the current archive, we only want an asset with signed binaries from this point forward
|
||||
rm "$archive_abs_path"
|
||||
|
||||
title "signing binaries found in the release archive"
|
||||
|
||||
discovered_binaries=0
|
||||
tmp_pipe=$(mktemp -ut pipe.XXX)
|
||||
mkfifo "$tmp_pipe"
|
||||
|
||||
find "$scratch_path" -perm +111 -type f > "$tmp_pipe" &
|
||||
|
||||
while IFS= read -r binary; do
|
||||
sign_binary "$binary" "$identity"
|
||||
((discovered_binaries++))
|
||||
done < "$tmp_pipe"
|
||||
|
||||
rm "$tmp_pipe"
|
||||
|
||||
if [ "$discovered_binaries" = "0" ]; then
|
||||
exit_with_error "found no binaries to sign"
|
||||
fi
|
||||
|
||||
title "recreating the release archive: $archive_abs_path"
|
||||
(cd "$scratch_path" && tar -czvf "$archive_abs_path" .)
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
archive_abs_path=$(realpath "$ARCHIVE_PATH")
|
||||
binary_abs_path=$(realpath "$BINARY_PATH")
|
||||
|
||||
if [ ! -f "$archive_abs_path" ]; then
|
||||
echo "archive does not exist: $archive_abs_path"
|
||||
if [ ! -f "$binary_abs_path" ]; then
|
||||
echo "archive does not exist: $binary_abs_path"
|
||||
fi
|
||||
|
||||
case "$IS_SNAPSHOT" in
|
||||
@@ -114,27 +72,23 @@ main() {
|
||||
MAC_SIGNING_IDENTITY=$(cat "$SCRIPT_DIR/$SIGNING_IDENTITY_FILENAME")
|
||||
|
||||
# sign all of the binaries in the archive and recreate the input archive with the signed binaries
|
||||
sign_binaries_in_archive "$archive_abs_path" "$MAC_SIGNING_IDENTITY"
|
||||
sign_binary "$binary_abs_path" "$MAC_SIGNING_IDENTITY"
|
||||
|
||||
# send all of the binaries off to apple to bless
|
||||
if $perform_notarization ; then
|
||||
notarize "$archive_abs_path"
|
||||
notarize "$binary_abs_path"
|
||||
else
|
||||
commentary "skipping notarization..."
|
||||
fi
|
||||
}
|
||||
|
||||
set +u
|
||||
if [ -z "$SCRIPT" ]
|
||||
then
|
||||
# capture all output from a subshell to log output additionally to a file (as well as the terminal)
|
||||
( (
|
||||
set +u
|
||||
if [ -n "$SKIP_SIGNING" ]; then
|
||||
commentary "skipping signing..."
|
||||
else
|
||||
set -u
|
||||
# log all output
|
||||
mkdir -p "$SCRIPT_DIR/log"
|
||||
/usr/bin/script "$SCRIPT_DIR/log/signing-$(basename $ARCHIVE_PATH).txt" /bin/bash -c "$0 $*"
|
||||
exit $?
|
||||
elif [ -n "$SKIP_SIGNING" ]; then
|
||||
commentary "skipping signing..."
|
||||
else
|
||||
set -u
|
||||
main
|
||||
fi
|
||||
main
|
||||
fi
|
||||
) 2>&1) | tee "$SCRIPT_DIR/log/signing-$(basename $BINARY_PATH)-$TARGET_NAME.txt"
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.16.x"
|
||||
GO_VERSION: "1.17.x"
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.16.x"
|
||||
GO_VERSION: "1.17.x"
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
+5
-18
@@ -41,6 +41,11 @@ builds:
|
||||
mod_timestamp: *build-timestamp
|
||||
env: *build-env
|
||||
ldflags: *build-ldflags
|
||||
hooks:
|
||||
post:
|
||||
# we must have signing as a build hook instead of the signs section. The signs section must register a new asset, where we want to replace an existing asset.
|
||||
# a post-build hook has the advantage of not needing to unpackage and repackage a tar.gz with a signed binary
|
||||
- ./.github/scripts/apple-signing/sign.sh "{{ .Path }}" "{{ .IsSnapshot }}" "{{ .Target }}"
|
||||
|
||||
- id: windows-build
|
||||
binary: syft
|
||||
@@ -67,16 +72,6 @@ archives:
|
||||
builds:
|
||||
- windows-build
|
||||
|
||||
signs:
|
||||
- artifacts: archive
|
||||
ids:
|
||||
- darwin-archives
|
||||
signature: "${artifact}"
|
||||
cmd: ./.github/scripts/apple-signing/sign.sh
|
||||
args:
|
||||
- "${artifact}"
|
||||
- "{{ .IsSnapshot }}"
|
||||
|
||||
nfpms:
|
||||
- license: "Apache 2.0"
|
||||
maintainer: "Anchore, Inc"
|
||||
@@ -129,17 +124,9 @@ dockers:
|
||||
docker_manifests:
|
||||
- name_template: anchore/syft:{{ .Tag }}
|
||||
image_templates:
|
||||
- anchore/syft:{{ .Tag }}-amd64
|
||||
- anchore/syft:v{{ .Major }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:{{ .Tag }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:{{ .Tag }}-amd64
|
||||
- anchore/syft:v{{ .Major }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:{{ .Tag }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
|
||||
@@ -6,6 +6,7 @@ COVER_TOTAL = $(RESULTSDIR)/unit-coverage-summary.txt
|
||||
LINTCMD = $(TEMPDIR)/golangci-lint run --tests=false --timeout=2m --config .golangci.yaml
|
||||
RELEASE_CMD=$(TEMPDIR)/goreleaser release --rm-dist
|
||||
SNAPSHOT_CMD=$(RELEASE_CMD) --skip-publish --snapshot
|
||||
VERSION=$(shell git describe --dirty --always --tags)
|
||||
COMPARE_TEST_IMAGE = centos:8.2.2004
|
||||
COMPARE_DIR = ./test/compare
|
||||
|
||||
@@ -55,6 +56,10 @@ ifndef SNAPSHOTDIR
|
||||
$(error SNAPSHOTDIR is not set)
|
||||
endif
|
||||
|
||||
ifndef VERSION
|
||||
$(error VERSION is not set)
|
||||
endif
|
||||
|
||||
ifndef REF_NAME
|
||||
REF_NAME = $(VERSION)
|
||||
endif
|
||||
@@ -104,7 +109,7 @@ bootstrap-tools: $(TEMPDIR)
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.42.1
|
||||
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
|
||||
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ v1.3.1
|
||||
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ v1.4.1
|
||||
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/neilpa/yajsv@v1.4.0
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
@@ -316,14 +321,17 @@ release: clean-dist CHANGELOG.md ## Build and publish final binaries and packag
|
||||
|
||||
rm -f .github/scripts/apple-signing/log/*.txt
|
||||
|
||||
# note: notarization cannot be done in parallel, thus --parallelism 1
|
||||
bash -c "\
|
||||
$(RELEASE_CMD) \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
--parallelism 1 \
|
||||
--release-notes <(cat CHANGELOG.md)\
|
||||
|| cat .github/scripts/apple-signing/log/*.txt && false"
|
||||
|| (cat .github/scripts/apple-signing/log/*.txt && false)"
|
||||
|
||||
cat .github/scripts/apple-signing/log/*.txt
|
||||
|
||||
# TODO: turn this into a post-release hook
|
||||
# upload the version file that supports the application version update check (excluding pre-releases)
|
||||
.github/scripts/update-version-file.sh "$(DISTDIR)" "$(VERSION)"
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@ If you encounter an issue, please [let us know using the issue tracker](https://
|
||||
|
||||
## Installation
|
||||
|
||||
**Note**: Currently, Syft is built only for Linux, macOS and Windows.
|
||||
|
||||
### Recommended
|
||||
```bash
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
```
|
||||
|
||||
...or, you can specify a release version and destination directory for the installation:
|
||||
... or, you can specify a release version and destination directory for the installation:
|
||||
|
||||
```
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>
|
||||
@@ -48,7 +50,21 @@ brew tap anchore/syft
|
||||
brew install syft
|
||||
```
|
||||
|
||||
**Note**: Currently, Syft is built only for macOS and Linux.
|
||||
### Nix
|
||||
|
||||
**Note**: nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/syft/default.nix)
|
||||
|
||||
Also syft is currently only in the [unstable channel](https://nixos.wiki/wiki/Nix_channels#The_official_channels) awaiting the `22.05` release
|
||||
|
||||
```bash
|
||||
nix-env -i syft
|
||||
```
|
||||
|
||||
... or, just try it out in an ephemeral nix shell
|
||||
|
||||
```bash
|
||||
nix-shell -p syft
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -192,6 +208,8 @@ The `volumeMounts` section mounts our secret to `/config`. The `volumes` section
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: syft-k8s-usage
|
||||
spec:
|
||||
containers:
|
||||
- image: anchore/syft:latest
|
||||
|
||||
@@ -140,6 +140,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
||||
)
|
||||
}
|
||||
|
||||
// NOTE(alex): Write a helper for the binding operation, which can be used to perform the binding but also double check that the intended effect was had or else return an error. Another thought is to somehow provide zero-valued defaults for all values in our config struct (maybe with reflection?). There may be a mechanism that already exists in viper that protects against this that I'm not aware of. ref: https://github.com/anchore/syft/pull/805#discussion_r801931192
|
||||
func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
||||
// Formatting & Input options //////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.4.0
|
||||
@@ -13,13 +13,9 @@ 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.0.0-20210922164639-b3fa992ebd29
|
||||
github.com/anchore/stereoscope v0.0.0-20220201190559-f162f1e96f45
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/anchore/stereoscope v0.0.0-20220214165125-25ebd49a842b
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/docker/docker v20.10.12+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/facebookincubator/nvdtools v0.1.4
|
||||
@@ -29,7 +25,6 @@ require (
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
@@ -40,7 +35,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.2
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spdx/tools-golang v0.1.0
|
||||
github.com/spdx/tools-golang v0.2.0
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
@@ -57,3 +52,65 @@ require (
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.10.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.12+incompatible // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/go-containerregistry v0.7.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.7 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211111162719-482062a4217b // indirect
|
||||
google.golang.org/grpc v1.42.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
@@ -111,8 +111,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.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk=
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0=
|
||||
github.com/anchore/stereoscope v0.0.0-20220201190559-f162f1e96f45 h1:GYwI1qXcGh7fmyUWLK41suUXYfmrvGWWKGh0cw+k6ug=
|
||||
github.com/anchore/stereoscope v0.0.0-20220201190559-f162f1e96f45/go.mod h1:QpDHHV2h1NNfu7klzU75XC8RvSlaPK6HHgi0dy8A6sk=
|
||||
github.com/anchore/stereoscope v0.0.0-20220214165125-25ebd49a842b h1:PMMXpTEHVVLErrXQ6mH9ocLAQyvQu/LUhdstrhx7AC4=
|
||||
github.com/anchore/stereoscope v0.0.0-20220214165125-25ebd49a842b/go.mod h1:QpDHHV2h1NNfu7klzU75XC8RvSlaPK6HHgi0dy8A6sk=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -759,8 +759,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.1.0 h1:iDMNEPqQk6CdiDj6eWDIDw85j0wQ3IR3pH9p0X05TSQ=
|
||||
github.com/spdx/tools-golang v0.1.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo=
|
||||
github.com/spdx/tools-golang v0.2.0 h1:KBNcw7xvVycRWeCWZK/5xQJA+plymW1+rTCs8ekJDro=
|
||||
github.com/spdx/tools-golang v0.2.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||
@@ -896,8 +896,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig=
|
||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
||||
@@ -15,4 +15,5 @@ type logging struct {
|
||||
|
||||
func (cfg logging) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("log.structured", false)
|
||||
v.SetDefault("log.file", "")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
const NONE = "NONE"
|
||||
const NOASSERTION = "NOASSERTION"
|
||||
|
||||
func DownloadLocation(p pkg.Package) string {
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
@@ -19,5 +22,5 @@ func DownloadLocation(p pkg.Package) string {
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
}
|
||||
}
|
||||
return "NOASSERTION"
|
||||
return NOASSERTION
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "NOASSERTION",
|
||||
expected: NOASSERTION,
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
@@ -43,7 +43,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
expected: NONE,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package spdxhelpers
|
||||
|
||||
type ReferenceCategory string
|
||||
|
||||
@@ -1,51 +1,26 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func ExternalRefs(p pkg.Package) (externalRefs []model.ExternalRef) {
|
||||
externalRefs = make([]model.ExternalRef, 0)
|
||||
func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
|
||||
externalRefs = make([]ExternalRef, 0)
|
||||
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, model.ExternalRef{
|
||||
ReferenceCategory: model.SecurityReferenceCategory,
|
||||
externalRefs = append(externalRefs, ExternalRef{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: pkg.CPEString(c),
|
||||
ReferenceType: model.Cpe23ExternalRefType,
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
if p.PURL != "" {
|
||||
externalRefs = append(externalRefs, model.ExternalRef{
|
||||
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||
externalRefs = append(externalRefs, ExternalRef{
|
||||
ReferenceCategory: PackageManagerReferenceCategory,
|
||||
ReferenceLocator: p.PURL,
|
||||
ReferenceType: model.PurlExternalRefType,
|
||||
ReferenceType: PurlExternalRefType,
|
||||
})
|
||||
}
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
func ExtractPURL(refs []model.ExternalRef) string {
|
||||
for _, r := range refs {
|
||||
if r.ReferenceType == model.PurlExternalRefType {
|
||||
return r.ReferenceLocator
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExtractCPEs(refs []model.ExternalRef) (cpes []pkg.CPE) {
|
||||
for _, r := range refs {
|
||||
if r.ReferenceType == model.Cpe23ExternalRefType {
|
||||
cpe, err := pkg.NewCPE(r.ReferenceLocator)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.ReferenceLocator, err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, cpe)
|
||||
}
|
||||
}
|
||||
return cpes
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package spdxhelpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -13,7 +12,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []model.ExternalRef
|
||||
expected []ExternalRef
|
||||
}{
|
||||
{
|
||||
name: "cpe + purl",
|
||||
@@ -23,16 +22,16 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
},
|
||||
PURL: "a-purl",
|
||||
},
|
||||
expected: []model.ExternalRef{
|
||||
expected: []ExternalRef{
|
||||
{
|
||||
ReferenceCategory: model.SecurityReferenceCategory,
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: pkg.CPEString(testCPE),
|
||||
ReferenceType: model.Cpe23ExternalRefType,
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||
ReferenceCategory: PackageManagerReferenceCategory,
|
||||
ReferenceLocator: "a-purl",
|
||||
ReferenceType: model.PurlExternalRefType,
|
||||
ReferenceType: PurlExternalRefType,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package spdxhelpers
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
DocumentationFileType FileType = "DOCUMENTATION" // if the file serves as documentation
|
||||
ImageFileType FileType = "IMAGE" // if the file is associated with a picture image file (MIME type of image/*, e.g., .jpg, .gif)
|
||||
VideoFileType FileType = "VIDEO" // if the file is associated with a video file type (MIME type of video/*)
|
||||
ArchiveFileType FileType = "ARCHIVE" // if the file represents an archive (.tar, .jar, etc.)
|
||||
SpdxFileType FileType = "SPDX" // if the file is an SPDX document
|
||||
ApplicationFileType FileType = "APPLICATION" // if the file is associated with a specific application type (MIME type of application/*)
|
||||
SourceFileType FileType = "SOURCE" // if the file is human readable source code (.c, .html, etc.)
|
||||
BinaryFileType FileType = "BINARY" // if the file is a compiled object, target image or binary executable (.o, .a, etc.)
|
||||
TextFileType FileType = "TEXT" // if the file is human readable text file (MIME type of text/*)
|
||||
AudioFileType FileType = "AUDIO" // if the file is associated with an audio file (MIME type of audio/* , e.g. .mp3)
|
||||
OtherFileType FileType = "OTHER" // if the file doesn't fit into the above categories (generated artifacts, data files, etc.)
|
||||
)
|
||||
@@ -18,7 +18,7 @@ func License(p pkg.Package) string {
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if len(p.Licenses) == 0 {
|
||||
return "NONE"
|
||||
return NONE
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
@@ -30,7 +30,7 @@ func License(p pkg.Package) string {
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
return "NOASSERTION"
|
||||
return NOASSERTION
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
|
||||
@@ -16,7 +16,7 @@ func Test_License(t *testing.T) {
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: "NONE",
|
||||
expected: NONE,
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
@@ -25,7 +25,7 @@ func Test_License(t *testing.T) {
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: "NOASSERTION",
|
||||
expected: NOASSERTION,
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
func NoneIfEmpty(value string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return "NONE"
|
||||
return NONE
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ func Test_noneIfEmpty(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
value: "",
|
||||
expected: "NONE",
|
||||
expected: NONE,
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
value: " ",
|
||||
expected: "NONE",
|
||||
expected: NONE,
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
value: "\t",
|
||||
expected: "NONE",
|
||||
expected: NONE,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package spdxhelpers
|
||||
|
||||
// source: https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/
|
||||
type RelationshipType string
|
||||
|
||||
const (
|
||||
// DescribedByRelationship is to be used when SPDXRef-A is described by SPDXREF-Document.
|
||||
// Example: The package 'WildFly' is described by SPDX document WildFly.spdx.
|
||||
DescribedByRelationship RelationshipType = "DESCRIBED_BY"
|
||||
|
||||
// ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.
|
||||
// Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c.
|
||||
ContainsRelationship RelationshipType = "CONTAINS"
|
||||
|
||||
// ContainedByRelationship is to be used when SPDXRef-A is contained by SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c is contained by ARCHIVE file bar.tgz
|
||||
ContainedByRelationship RelationshipType = "CONTAINED_BY"
|
||||
|
||||
// DependsOnRelationship is to be used when SPDXRef-A depends on SPDXRef-B.
|
||||
// Example: Package A depends on the presence of package B in order to build and run
|
||||
DependsOnRelationship RelationshipType = "DEPENDS_ON"
|
||||
|
||||
// DependencyOfRelationship is to be used when SPDXRef-A is dependency of SPDXRef-B.
|
||||
// Example: A is explicitly stated as a dependency of B in a machine-readable file. Use when a package manager does not define scopes.
|
||||
DependencyOfRelationship RelationshipType = "DEPENDENCY_OF"
|
||||
|
||||
// DependencyManifestOfRelationship is to be used when SPDXRef-A is a manifest file that lists a set of dependencies for SPDXRef-B.
|
||||
// Example: A file package.json is the dependency manifest of a package foo. Note that only one manifest should be used to define the same dependency graph.
|
||||
DependencyManifestOfRelationship RelationshipType = "DEPENDENCY_MANIFEST_OF"
|
||||
|
||||
// BuildDependencyOfRelationship is to be used when SPDXRef-A is a build dependency of SPDXRef-B.
|
||||
// Example: A is in the compile scope of B in a Maven project.
|
||||
BuildDependencyOfRelationship RelationshipType = "BUILD_DEPENDENCY_OF"
|
||||
|
||||
// DevDependencyOfRelationship is to be used when SPDXRef-A is a development dependency of SPDXRef-B.
|
||||
// Example: A is in the devDependencies scope of B in a Maven project.
|
||||
DevDependencyOfRelationship RelationshipType = "DEV_DEPENDENCY_OF"
|
||||
|
||||
// OptionalDependencyOfRelationship is to be used when SPDXRef-A is an optional dependency of SPDXRef-B.
|
||||
// Example: Use when building the code will proceed even if a dependency cannot be found, fails to install, or is only installed on a specific platform. For example, A is in the optionalDependencies scope of npm project B.
|
||||
OptionalDependencyOfRelationship RelationshipType = "OPTIONAL_DEPENDENCY_OF"
|
||||
|
||||
// ProvidedDependencyOfRelationship is to be used when SPDXRef-A is a to be provided dependency of SPDXRef-B.
|
||||
// Example: A is in the provided scope of B in a Maven project, indicating that the project expects it to be provided, for instance, by the container or JDK.
|
||||
ProvidedDependencyOfRelationship RelationshipType = "PROVIDED_DEPENDENCY_OF"
|
||||
|
||||
// TestDependencyOfRelationship is to be used when SPDXRef-A is a test dependency of SPDXRef-B.
|
||||
// Example: A is in the test scope of B in a Maven project.
|
||||
TestDependencyOfRelationship RelationshipType = "TEST_DEPENDENCY_OF"
|
||||
|
||||
// RuntimeDependencyOfRelationship is to be used when SPDXRef-A is a dependency required for the execution of SPDXRef-B.
|
||||
// Example: A is in the runtime scope of B in a Maven project.
|
||||
RuntimeDependencyOfRelationship RelationshipType = "RUNTIME_DEPENDENCY_OF"
|
||||
|
||||
// ExampleOfRelationship is to be used when SPDXRef-A is an example of SPDXRef-B.
|
||||
// Example: The file or snippet that illustrates how to use an application or library.
|
||||
ExampleOfRelationship RelationshipType = "EXAMPLE_OF"
|
||||
|
||||
// GeneratesRelationship is to be used when SPDXRef-A generates SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk generates a BINARY file a.out
|
||||
GeneratesRelationship RelationshipType = "GENERATES"
|
||||
|
||||
// GeneratedFromRelationship is to be used when SPDXRef-A was generated from SPDXRef-B.
|
||||
// Example: A BINARY file a.out has been generated from a SOURCE file makefile.mk. A BINARY file foolib.a is generated from a SOURCE file bar.c.
|
||||
GeneratedFromRelationship RelationshipType = "GENERATED_FROM"
|
||||
|
||||
// AncestorOfRelationship is to be used when SPDXRef-A is an ancestor (same lineage but pre-dates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is a version of the original ancestor SOURCE file 'makefile2.mk'
|
||||
AncestorOfRelationship RelationshipType = "ANCESTOR_OF"
|
||||
|
||||
// DescendantOfRelationship is to be used when SPDXRef-A is a descendant of (same lineage but postdates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a descendant of the original SOURCE file 'makefile.mk'
|
||||
DescendantOfRelationship RelationshipType = "DESCENDANT_OF"
|
||||
|
||||
// VariantOfRelationship is to be used when SPDXRef-A is a variant of (same lineage but not clear which came first) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a variant of SOURCE file makefile.mk if they differ by some edit, but there is no way to tell which came first (no reliable date information).
|
||||
VariantOfRelationship RelationshipType = "VARIANT_OF"
|
||||
|
||||
// DistributionArtifactRelationship is to be used when distributing SPDXRef-A requires that SPDXRef-B also be distributed.
|
||||
// Example: A BINARY file foo.o requires that the ARCHIVE file bar-sources.tgz be made available on distribution.
|
||||
DistributionArtifactRelationship RelationshipType = "DISTRIBUTION_ARTIFACT"
|
||||
|
||||
// PatchForRelationship is to be used when SPDXRef-A is a patch file for (to be applied to) SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file for SOURCE file foo.c.
|
||||
PatchForRelationship RelationshipType = "PATCH_FOR"
|
||||
|
||||
// PatchAppliedRelationship is to be used when SPDXRef-A is a patch file that has been applied to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file that has been applied to SOURCE file 'foo-patched.c'.
|
||||
PatchAppliedRelationship RelationshipType = "PATCH_APPLIED"
|
||||
|
||||
// CopyOfRelationship is to be used when SPDXRef-A is an exact copy of SPDXRef-B.
|
||||
// Example: A BINARY file alib.a is an exact copy of BINARY file a2lib.a.
|
||||
CopyOfRelationship RelationshipType = "COPY_OF"
|
||||
|
||||
// FileAddedRelationship is to be used when SPDXRef-A is a file that was added to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been added to package ARCHIVE bar.tgz.
|
||||
FileAddedRelationship RelationshipType = "FILE_ADDED"
|
||||
|
||||
// FileDeletedRelationship is to be used when SPDXRef-A is a file that was deleted from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff has been deleted from package ARCHIVE bar.tgz.
|
||||
FileDeletedRelationship RelationshipType = "FILE_DELETED"
|
||||
|
||||
// FileModifiedRelationship is to be used when SPDXRef-A is a file that was modified from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been modified from SOURCE file foo.orig.c.
|
||||
FileModifiedRelationship RelationshipType = "FILE_MODIFIED"
|
||||
|
||||
// ExpandedFromArchiveRelationship is to be used when SPDXRef-A is expanded from the archive SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c, has been expanded from the archive ARCHIVE file xyz.tgz.
|
||||
ExpandedFromArchiveRelationship RelationshipType = "EXPANDED_FROM_ARCHIVE"
|
||||
|
||||
// DynamicLinkRelationship is to be used when SPDXRef-A dynamically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' dynamically links to BINARY file zlib.so.
|
||||
DynamicLinkRelationship RelationshipType = "DYNAMIC_LINK"
|
||||
|
||||
// StaticLinkRelationship is to be used when SPDXRef-A statically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' statically links to BINARY zlib.a.
|
||||
StaticLinkRelationship RelationshipType = "STATIC_LINK"
|
||||
|
||||
// DataFileOfRelationship is to be used when SPDXRef-A is a data file used in SPDXRef-B.
|
||||
// Example: An IMAGE file 'kitty.jpg' is a data file of an APPLICATION 'hellokitty'.
|
||||
DataFileOfRelationship RelationshipType = "DATA_FILE_OF"
|
||||
|
||||
// TestCaseOfRelationship is to be used when SPDXRef-A is a test case used in testing SPDXRef-B.
|
||||
// Example: A SOURCE file testMyCode.java is a unit test file used to test an APPLICATION MyPackage.
|
||||
TestCaseOfRelationship RelationshipType = "TEST_CASE_OF"
|
||||
|
||||
// BuildToolOfRelationship is to be used when SPDXRef-A is used to build SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is used to build an APPLICATION 'zlib'.
|
||||
BuildToolOfRelationship RelationshipType = "BUILD_TOOL_OF"
|
||||
|
||||
// DevToolOfRelationship is to be used when SPDXRef-A is used as a development tool for SPDXRef-B.
|
||||
// Example: Any tool used for development such as a code debugger.
|
||||
DevToolOfRelationship RelationshipType = "DEV_TOOL_OF"
|
||||
|
||||
// TestOfRelationship is to be used when SPDXRef-A is used for testing SPDXRef-B.
|
||||
// Example: Generic relationship for cases where it's clear that something is used for testing but unclear whether it's TEST_CASE_OF or TEST_TOOL_OF.
|
||||
TestOfRelationship RelationshipType = "TEST_OF"
|
||||
|
||||
// TestToolOfRelationship is to be used when SPDXRef-A is used as a test tool for SPDXRef-B.
|
||||
// Example: Any tool used to test the code such as ESlint.
|
||||
TestToolOfRelationship RelationshipType = "TEST_TOOL_OF"
|
||||
|
||||
// DocumentationOfRelationship is to be used when SPDXRef-A provides documentation of SPDXRef-B.
|
||||
// Example: A DOCUMENTATION file readme.txt documents the APPLICATION 'zlib'.
|
||||
DocumentationOfRelationship RelationshipType = "DOCUMENTATION_OF"
|
||||
|
||||
// OptionalComponentOfRelationship is to be used when SPDXRef-A is an optional component of SPDXRef-B.
|
||||
// Example: A SOURCE file fool.c (which is in the contributors directory) may or may not be included in the build of APPLICATION 'atthebar'.
|
||||
OptionalComponentOfRelationship RelationshipType = "OPTIONAL_COMPONENT_OF"
|
||||
|
||||
// MetafileOfRelationship is to be used when SPDXRef-A is a metafile of SPDXRef-B.
|
||||
// Example: A SOURCE file pom.xml is a metafile of the APPLICATION 'Apache Xerces'.
|
||||
MetafileOfRelationship RelationshipType = "METAFILE_OF"
|
||||
|
||||
// PackageOfRelationship is to be used when SPDXRef-A is used as a package as part of SPDXRef-B.
|
||||
// Example: A Linux distribution contains an APPLICATION package gawk as part of the distribution MyLinuxDistro.
|
||||
PackageOfRelationship RelationshipType = "PACKAGE_OF"
|
||||
|
||||
// AmendsRelationship is to be used when (current) SPDXRef-DOCUMENT amends the SPDX information in SPDXRef-B.
|
||||
// Example: (Current) SPDX document A version 2 contains a correction to a previous version of the SPDX document A version 1. Note the reserved identifier SPDXRef-DOCUMENT for the current document is required.
|
||||
AmendsRelationship RelationshipType = "AMENDS"
|
||||
|
||||
// PrerequisiteForRelationship is to be used when SPDXRef-A is a prerequisite for SPDXRef-B.
|
||||
// Example: A library bar.dll is a prerequisite or dependency for APPLICATION foo.exe
|
||||
PrerequisiteForRelationship RelationshipType = "PREREQUISITE_FOR"
|
||||
|
||||
// HasPrerequisiteRelationship is to be used when SPDXRef-A has as a prerequisite SPDXRef-B.
|
||||
// Example: An APPLICATION foo.exe has prerequisite or dependency on bar.dll
|
||||
HasPrerequisiteRelationship RelationshipType = "HAS_PREREQUISITE"
|
||||
|
||||
// OtherRelationship is to be used for a relationship which has not been defined in the formal SPDX specification. A description of the relationship should be included in the Relationship comments field.
|
||||
OtherRelationship RelationshipType = "OTHER"
|
||||
)
|
||||
@@ -0,0 +1,339 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
|
||||
spdxIDMap := make(map[string]interface{})
|
||||
|
||||
s := &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{},
|
||||
FileDigests: map[source.Coordinates][]file.Digest{},
|
||||
LinuxDistribution: findLinuxReleaseByPURL(doc),
|
||||
},
|
||||
}
|
||||
|
||||
collectSyftPackages(s, spdxIDMap, doc)
|
||||
|
||||
collectSyftFiles(s, spdxIDMap, doc)
|
||||
|
||||
s.Relationships = toSyftRelationships(spdxIDMap, doc)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release {
|
||||
for _, p := range doc.Packages {
|
||||
purlValue := findPURLValue(p)
|
||||
if purlValue == "" {
|
||||
continue
|
||||
}
|
||||
purl, err := packageurl.FromString(purlValue)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse purl: %s", purlValue)
|
||||
continue
|
||||
}
|
||||
distro := findQualifierValue(purl, pkg.PURLQualifierDistro)
|
||||
if distro != "" {
|
||||
parts := strings.Split(distro, "-")
|
||||
name := parts[0]
|
||||
version := ""
|
||||
if len(parts) > 1 {
|
||||
version = parts[1]
|
||||
}
|
||||
return &linux.Release{
|
||||
PrettyName: name,
|
||||
Name: name,
|
||||
ID: name,
|
||||
IDLike: []string{name},
|
||||
Version: version,
|
||||
VersionID: version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document2_2) {
|
||||
for _, p := range doc.Packages {
|
||||
syftPkg := toSyftPackage(p)
|
||||
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
||||
s.Artifacts.PackageCatalog.Add(*syftPkg)
|
||||
}
|
||||
}
|
||||
|
||||
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document2_2) {
|
||||
for _, f := range doc.UnpackagedFiles {
|
||||
l := toSyftLocation(f)
|
||||
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
||||
|
||||
s.Artifacts.FileMetadata[l.Coordinates] = toFileMetadata(f)
|
||||
s.Artifacts.FileDigests[l.Coordinates] = toFileDigests(f)
|
||||
}
|
||||
}
|
||||
|
||||
func toFileDigests(f *spdx.File2_2) (digests []file.Digest) {
|
||||
for _, digest := range f.FileChecksums {
|
||||
digests = append(digests, file.Digest{
|
||||
Algorithm: string(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
})
|
||||
}
|
||||
return digests
|
||||
}
|
||||
|
||||
func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) {
|
||||
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
||||
for _, typ := range f.FileType {
|
||||
switch FileType(typ) {
|
||||
case ImageFileType:
|
||||
meta.MIMEType = "image/"
|
||||
case VideoFileType:
|
||||
meta.MIMEType = "video/"
|
||||
case ApplicationFileType:
|
||||
meta.MIMEType = "application/"
|
||||
case TextFileType:
|
||||
meta.MIMEType = "text/"
|
||||
case AudioFileType:
|
||||
meta.MIMEType = "audio/"
|
||||
case BinaryFileType:
|
||||
case ArchiveFileType:
|
||||
case OtherFileType:
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2) []artifact.Relationship {
|
||||
var out []artifact.Relationship
|
||||
for _, r := range doc.Relationships {
|
||||
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
||||
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.CreationInfo.SPDXIdentifier) {
|
||||
log.Debugf("ignoring relationship to external document: %+v", r)
|
||||
continue
|
||||
}
|
||||
a := spdxIDMap[string(r.RefA.ElementRefID)]
|
||||
b := spdxIDMap[string(r.RefB.ElementRefID)]
|
||||
from, fromOk := a.(*pkg.Package)
|
||||
toPackage, toPackageOk := b.(*pkg.Package)
|
||||
toLocation, toLocationOk := b.(*source.Location)
|
||||
if !fromOk || !(toPackageOk || toLocationOk) {
|
||||
log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
|
||||
continue
|
||||
}
|
||||
var to artifact.Identifiable
|
||||
var typ artifact.RelationshipType
|
||||
if toLocationOk {
|
||||
if r.Relationship == string(ContainsRelationship) {
|
||||
typ = artifact.ContainsRelationship
|
||||
to = toLocation
|
||||
}
|
||||
} else {
|
||||
switch RelationshipType(r.Relationship) {
|
||||
case ContainsRelationship:
|
||||
typ = artifact.ContainsRelationship
|
||||
to = toPackage
|
||||
case BuildDependencyOfRelationship:
|
||||
typ = artifact.BuildDependencyOfRelationship
|
||||
to = toPackage
|
||||
case RuntimeDependencyOfRelationship:
|
||||
typ = artifact.RuntimeDependencyOfRelationship
|
||||
to = toPackage
|
||||
case OtherRelationship:
|
||||
// Encoding uses a specifically formatted comment...
|
||||
if strings.Index(r.RelationshipComment, string(artifact.OwnershipByFileOverlapRelationship)) == 0 {
|
||||
typ = artifact.RuntimeDependencyOfRelationship
|
||||
to = toPackage
|
||||
}
|
||||
}
|
||||
}
|
||||
if typ != "" && to != nil {
|
||||
out = append(out, artifact.Relationship{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: typ,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
|
||||
const layerIDPrefix = "layerID: "
|
||||
var fileSystemID string
|
||||
if strings.Index(f.FileComment, layerIDPrefix) == 0 {
|
||||
fileSystemID = strings.TrimPrefix(f.FileComment, layerIDPrefix)
|
||||
}
|
||||
if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 {
|
||||
fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix)
|
||||
}
|
||||
return source.Coordinates{
|
||||
RealPath: f.FileName,
|
||||
FileSystemID: fileSystemID,
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftLocation(f *spdx.File2_2) *source.Location {
|
||||
return &source.Location{
|
||||
Coordinates: toSyftCoordinates(f),
|
||||
VirtualPath: f.FileName,
|
||||
}
|
||||
}
|
||||
|
||||
func requireAndTrimPrefix(val interface{}, prefix string) string {
|
||||
if v, ok := val.(string); ok {
|
||||
if i := strings.Index(v, prefix); i == 0 {
|
||||
return strings.Replace(v, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type pkgInfo struct {
|
||||
purl packageurl.PackageURL
|
||||
typ pkg.Type
|
||||
lang pkg.Language
|
||||
}
|
||||
|
||||
func (p *pkgInfo) qualifierValue(name string) string {
|
||||
return findQualifierValue(p.purl, name)
|
||||
}
|
||||
|
||||
func findQualifierValue(purl packageurl.PackageURL, qualifier string) string {
|
||||
for _, q := range purl.Qualifiers {
|
||||
if q.Key == qualifier {
|
||||
return q.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractPkgInfo(p *spdx.Package2_2) pkgInfo {
|
||||
pu := findPURLValue(p)
|
||||
purl, err := packageurl.FromString(pu)
|
||||
if err != nil {
|
||||
return pkgInfo{}
|
||||
}
|
||||
return pkgInfo{
|
||||
purl,
|
||||
pkg.TypeByName(purl.Type),
|
||||
pkg.LanguageByName(purl.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftPackage(p *spdx.Package2_2) *pkg.Package {
|
||||
info := extractPkgInfo(p)
|
||||
metadataType, metadata := extractMetadata(p, info)
|
||||
sP := pkg.Package{
|
||||
Type: info.typ,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Licenses: parseLicense(p.PackageLicenseDeclared),
|
||||
CPEs: extractCPEs(p),
|
||||
PURL: info.purl.String(),
|
||||
Language: info.lang,
|
||||
MetadataType: metadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
sP.SetID()
|
||||
|
||||
return &sP
|
||||
}
|
||||
|
||||
func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interface{}) {
|
||||
arch := info.qualifierValue(pkg.PURLQualifierArch)
|
||||
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
|
||||
upstream := strings.SplitN(upstreamValue, "@", 2)
|
||||
upstreamName := upstream[0]
|
||||
upstreamVersion := ""
|
||||
if len(upstream) > 1 {
|
||||
upstreamVersion = upstream[1]
|
||||
}
|
||||
switch info.typ {
|
||||
case pkg.ApkPkg:
|
||||
return pkg.ApkMetadataType, pkg.ApkMetadata{
|
||||
Package: p.PackageName,
|
||||
OriginPackage: upstreamName,
|
||||
Maintainer: p.PackageSupplierPerson,
|
||||
Version: p.PackageVersion,
|
||||
License: p.PackageLicenseDeclared,
|
||||
Architecture: arch,
|
||||
URL: p.PackageHomePage,
|
||||
Description: p.PackageDescription,
|
||||
}
|
||||
case pkg.RpmPkg:
|
||||
converted, err := strconv.Atoi(info.qualifierValue(pkg.PURLQualifierEpoch))
|
||||
var epoch *int
|
||||
if err != nil {
|
||||
epoch = nil
|
||||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
return pkg.RpmdbMetadataType, pkg.RpmdbMetadata{
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Epoch: epoch,
|
||||
Arch: arch,
|
||||
SourceRpm: upstreamValue,
|
||||
License: p.PackageLicenseConcluded,
|
||||
Vendor: p.PackageOriginatorOrganization,
|
||||
}
|
||||
case pkg.DebPkg:
|
||||
return pkg.DpkgMetadataType, pkg.DpkgMetadata{
|
||||
Package: p.PackageName,
|
||||
Source: upstreamName,
|
||||
Version: p.PackageVersion,
|
||||
SourceVersion: upstreamVersion,
|
||||
Architecture: arch,
|
||||
Maintainer: p.PackageOriginatorPerson,
|
||||
}
|
||||
}
|
||||
return pkg.UnknownMetadataType, nil
|
||||
}
|
||||
|
||||
func findPURLValue(p *spdx.Package2_2) string {
|
||||
for _, r := range p.PackageExternalReferences {
|
||||
if r.RefType == string(PurlExternalRefType) {
|
||||
return r.Locator
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) {
|
||||
for _, r := range p.PackageExternalReferences {
|
||||
if r.RefType == string(Cpe23ExternalRefType) {
|
||||
cpe, err := pkg.NewCPE(r.Locator)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, cpe)
|
||||
}
|
||||
}
|
||||
return cpes
|
||||
}
|
||||
|
||||
func parseLicense(l string) []string {
|
||||
if l == NOASSERTION || l == NONE {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(l, " AND ")
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToSyftModel(t *testing.T) {
|
||||
sbom, err := ToSyftModel(&spdx.Document2_2{
|
||||
CreationInfo: &spdx.CreationInfo2_2{
|
||||
SPDXVersion: "1",
|
||||
DataLicense: "GPL",
|
||||
SPDXIdentifier: "id-doc-1",
|
||||
DocumentName: "docName",
|
||||
DocumentNamespace: "docNamespace",
|
||||
ExternalDocumentReferences: nil,
|
||||
LicenseListVersion: "",
|
||||
CreatorPersons: nil,
|
||||
CreatorOrganizations: nil,
|
||||
CreatorTools: nil,
|
||||
Created: "",
|
||||
CreatorComment: "",
|
||||
DocumentComment: "",
|
||||
},
|
||||
Packages: map[spdx.ElementID]*spdx.Package2_2{
|
||||
"id-pkg-1": {
|
||||
PackageName: "pkg-1",
|
||||
PackageSPDXIdentifier: "id-pkg-1",
|
||||
PackageVersion: "5.4.3",
|
||||
PackageSupplierPerson: "",
|
||||
PackageSupplierOrganization: "",
|
||||
PackageLicenseDeclared: "",
|
||||
PackageDescription: "",
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
||||
{
|
||||
Category: "SECURITY",
|
||||
Locator: "cpe:2.3:a:pkg-1:pkg-1:5.4.3:*:*:*:*:*:*:*",
|
||||
RefType: "cpe23Type",
|
||||
},
|
||||
{
|
||||
Category: "SECURITY",
|
||||
Locator: "cpe:2.3:a:pkg_1:pkg_1:5.4.3:*:*:*:*:*:*:*",
|
||||
RefType: "cpe23Type",
|
||||
},
|
||||
{
|
||||
Category: "PACKAGE_MANAGER",
|
||||
Locator: "pkg:alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
Files: nil,
|
||||
},
|
||||
"id-pkg-2": {
|
||||
PackageName: "pkg-2",
|
||||
PackageSPDXIdentifier: "id-pkg-2",
|
||||
PackageVersion: "7.3.1",
|
||||
PackageSupplierPerson: "",
|
||||
PackageSupplierOrganization: "",
|
||||
PackageLicenseDeclared: "",
|
||||
PackageDescription: "",
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
||||
{
|
||||
Category: "SECURITY",
|
||||
Locator: "cpe:2.3:a:pkg-2:pkg-2:7.3.1:*:*:*:*:*:*:*",
|
||||
RefType: "cpe23Type",
|
||||
},
|
||||
{
|
||||
Category: "SECURITY",
|
||||
Locator: "cpe:2.3:a:pkg_2:pkg_2:7.3.1:*:*:*:*:*:*:*",
|
||||
RefType: "cpe23Type",
|
||||
},
|
||||
{
|
||||
Category: "SECURITY",
|
||||
Locator: "cpe:2.3:a:pkg-2:pkg_2:7.3.1:*:*:*:*:*:*:*",
|
||||
RefType: "cpe23Type",
|
||||
},
|
||||
{
|
||||
Category: "PACKAGE_MANAGER",
|
||||
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=p2-origin@9.1.3&distro=debian-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
Files: nil,
|
||||
},
|
||||
},
|
||||
UnpackagedFiles: map[spdx.ElementID]*spdx.File2_2{},
|
||||
Relationships: []*spdx.Relationship2_2{},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, sbom)
|
||||
|
||||
pkgs := sbom.Artifacts.PackageCatalog.Sorted()
|
||||
|
||||
assert.Len(t, pkgs, 2)
|
||||
|
||||
p1 := pkgs[0]
|
||||
assert.Equal(t, p1.Name, "pkg-1")
|
||||
assert.Equal(t, p1.MetadataType, pkg.ApkMetadataType)
|
||||
p1meta := p1.Metadata.(pkg.ApkMetadata)
|
||||
assert.Equal(t, p1meta.OriginPackage, "p1-origin")
|
||||
assert.Len(t, p1.CPEs, 2)
|
||||
|
||||
p2 := pkgs[1]
|
||||
assert.Equal(t, p2.Name, "pkg-2")
|
||||
assert.Equal(t, p2.MetadataType, pkg.DpkgMetadataType)
|
||||
p2meta := p2.Metadata.(pkg.DpkgMetadata)
|
||||
assert.Equal(t, p2meta.Source, "p2-origin")
|
||||
assert.Equal(t, p2meta.SourceVersion, "9.1.3")
|
||||
assert.Len(t, p2.CPEs, 3)
|
||||
}
|
||||
|
||||
func Test_extractMetadata(t *testing.T) {
|
||||
oneTwoThreeFour := 1234
|
||||
tests := []struct {
|
||||
pkg spdx.Package2_2
|
||||
metaType pkg.MetadataType
|
||||
meta interface{}
|
||||
}{
|
||||
{
|
||||
pkg: spdx.Package2_2{
|
||||
PackageName: "SomeDebPkg",
|
||||
PackageVersion: "43.1.235",
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
||||
{
|
||||
Category: "PACKAGE_MANAGER",
|
||||
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=somedebpkg-origin@9.1.3&distro=debian-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.DpkgMetadataType,
|
||||
meta: pkg.DpkgMetadata{
|
||||
Package: "SomeDebPkg",
|
||||
Source: "somedebpkg-origin",
|
||||
Version: "43.1.235",
|
||||
SourceVersion: "9.1.3",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
{
|
||||
pkg: spdx.Package2_2{
|
||||
PackageName: "SomeApkPkg",
|
||||
PackageVersion: "3.2.9",
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
||||
{
|
||||
Category: "PACKAGE_MANAGER",
|
||||
Locator: "pkg:alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.ApkMetadataType,
|
||||
meta: pkg.ApkMetadata{
|
||||
Package: "SomeApkPkg",
|
||||
OriginPackage: "apk-origin",
|
||||
Version: "3.2.9",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
{
|
||||
pkg: spdx.Package2_2{
|
||||
PackageName: "SomeRpmPkg",
|
||||
PackageVersion: "13.2.79",
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
||||
{
|
||||
Category: "PACKAGE_MANAGER",
|
||||
Locator: "pkg:rpm/pkg-2@7.3.1?arch=x86_64&epoch=1234&upstream=some-rpm-origin-1.16.3&distro=alpine-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.RpmdbMetadataType,
|
||||
meta: pkg.RpmdbMetadata{
|
||||
Name: "SomeRpmPkg",
|
||||
Version: "13.2.79",
|
||||
Epoch: &oneTwoThreeFour,
|
||||
Arch: "x86_64",
|
||||
Release: "",
|
||||
SourceRpm: "some-rpm-origin-1.16.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.pkg.PackageName, func(t *testing.T) {
|
||||
info := extractPkgInfo(&test.pkg)
|
||||
metaType, meta := extractMetadata(&test.pkg, info)
|
||||
assert.Equal(t, test.metaType, metaType)
|
||||
assert.EqualValues(t, test.meta, meta)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spdx/tools-golang/jsonloader"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (s *sbom.SBOM, err error) {
|
||||
defer func() {
|
||||
// The spdx tools JSON parser panics in quite a lot of situations, just handle this as a parse failure
|
||||
if v := recover(); v != nil {
|
||||
s = nil
|
||||
err = fmt.Errorf("an error occurred during SPDX JSON document parsing: %+v", v)
|
||||
}
|
||||
}()
|
||||
|
||||
doc, err := jsonloader.Load2_2(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||
}
|
||||
|
||||
return spdxhelpers.ToSyftModel(doc)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSPDXJSONDecoder(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
fail bool
|
||||
packages []string
|
||||
relationships []string
|
||||
}{
|
||||
{
|
||||
path: "alpine-3.10.syft.spdx.json",
|
||||
packages: []string{"busybox", "libssl1.1", "ssl_client"},
|
||||
relationships: []string{"busybox", "busybox", "libssl1.1", "libssl1.1"},
|
||||
},
|
||||
{
|
||||
path: "alpine-3.10.vendor.spdx.json",
|
||||
packages: []string{"alpine", "busybox", "ssl_client"},
|
||||
relationships: []string{},
|
||||
},
|
||||
{
|
||||
path: "example7-bin.spdx.json",
|
||||
},
|
||||
{
|
||||
path: "example7-go-module.spdx.json",
|
||||
},
|
||||
{
|
||||
path: "example7-golang.spdx.json",
|
||||
},
|
||||
{
|
||||
path: "example7-third-party-modules.spdx.json",
|
||||
},
|
||||
{
|
||||
path: "bad/example7-bin.spdx.json",
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
path: "bad/example7-go-module.spdx.json",
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
path: "bad/example7-golang.spdx.json",
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
path: "bad/example7-third-party-modules.spdx.json",
|
||||
fail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
f, err := os.Open("test-fixtures/spdx/" + test.path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sbom, err := decoder(f)
|
||||
|
||||
if test.fail {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if test.packages != nil {
|
||||
assert.Equal(t, sbom.Artifacts.PackageCatalog.PackageCount(), len(test.packages))
|
||||
|
||||
packages:
|
||||
for _, pkgName := range test.packages {
|
||||
for _, p := range sbom.Artifacts.PackageCatalog.Sorted() {
|
||||
if p.Name == pkgName {
|
||||
continue packages
|
||||
}
|
||||
}
|
||||
assert.NoError(t, fmt.Errorf("Unable to find package: %s", pkgName))
|
||||
}
|
||||
}
|
||||
|
||||
if test.relationships != nil {
|
||||
assert.Len(t, sbom.Relationships, len(test.relationships))
|
||||
|
||||
relationships:
|
||||
for _, pkgName := range test.relationships {
|
||||
for _, rel := range sbom.Relationships {
|
||||
p, ok := rel.From.(*pkg.Package)
|
||||
if ok && p.Name == pkgName {
|
||||
continue relationships
|
||||
}
|
||||
}
|
||||
assert.NoError(t, fmt.Errorf("Unable to find relationship: %s", pkgName))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package spdx22json
|
||||
|
||||
import "github.com/anchore/syft/syft/format"
|
||||
|
||||
// note: this format is LOSSY relative to the syftjson formation, which means that decoding and validation is not supported at this time
|
||||
// note: this format is LOSSY relative to the syftjson format
|
||||
func Format() format.Format {
|
||||
return format.NewFormat(
|
||||
format.SPDXJSONOption,
|
||||
encoder,
|
||||
nil,
|
||||
nil,
|
||||
decoder,
|
||||
validator,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
package model
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
DocumentationFileType FileType = "DOCUMENTATION" // if the file serves as documentation
|
||||
ImageFileType FileType = "IMAGE" // if the file is associated with a picture image file (MIME type of image/*, e.g., .jpg, .gif)
|
||||
VideoFileType FileType = "VIDEO" // if the file is associated with a video file type (MIME type of video/*)
|
||||
ArchiveFileType FileType = "ARCHIVE" // if the file represents an archive (.tar, .jar, etc.)
|
||||
SpdxFileType FileType = "SPDX" // if the file is an SPDX document
|
||||
ApplicationFileType FileType = "APPLICATION" // if the file is associated with a specific application type (MIME type of application/*)
|
||||
SourceFileType FileType = "SOURCE" // if the file is human readable source code (.c, .html, etc.)
|
||||
BinaryFileType FileType = "BINARY" // if the file is a compiled object, target image or binary executable (.o, .a, etc.)
|
||||
TextFileType FileType = "TEXT" // if the file is human readable text file (MIME type of text/*)
|
||||
AudioFileType FileType = "AUDIO" // if the file is associated with an audio file (MIME type of audio/* , e.g. .mp3)
|
||||
OtherFileType FileType = "OTHER" // if the file doesn't fit into the above categories (generated artifacts, data files, etc.)
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Item
|
||||
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
|
||||
type Package struct {
|
||||
Item
|
||||
// The checksum property provides a mechanism that can be used to verify that the contents of a File or
|
||||
@@ -14,7 +16,7 @@ type Package struct {
|
||||
DownloadLocation string `json:"downloadLocation,omitempty"`
|
||||
// An External Reference allows a Package to reference an external source of additional information, metadata,
|
||||
// enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.
|
||||
ExternalRefs []ExternalRef `json:"externalRefs,omitempty"`
|
||||
ExternalRefs []spdxhelpers.ExternalRef `json:"externalRefs,omitempty"`
|
||||
// Indicates whether the file content of this package has been available for or subjected to analysis when
|
||||
// creating the SPDX document. If false indicates packages that represent metadata or URI references to a
|
||||
// project, product, artifact, distribution or a component. If set to false, the package must not contain any files
|
||||
|
||||
@@ -1,183 +1,13 @@
|
||||
package model
|
||||
|
||||
import "github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
|
||||
type Relationship struct {
|
||||
// Id to which the SPDX element is related
|
||||
SpdxElementID string `json:"spdxElementId"`
|
||||
// Describes the type of relationship between two SPDX elements.
|
||||
RelationshipType RelationshipType `json:"relationshipType"`
|
||||
RelationshipType spdxhelpers.RelationshipType `json:"relationshipType"`
|
||||
// SPDX ID for SpdxElement. A related SpdxElement.
|
||||
RelatedSpdxElement string `json:"relatedSpdxElement"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// source: https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/
|
||||
type RelationshipType string
|
||||
|
||||
const (
|
||||
// DescribedByRelationship is to be used when SPDXRef-A is described by SPDXREF-Document.
|
||||
// Example: The package 'WildFly' is described by SPDX document WildFly.spdx.
|
||||
DescribedByRelationship RelationshipType = "DESCRIBED_BY"
|
||||
|
||||
// ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.
|
||||
// Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c.
|
||||
ContainsRelationship RelationshipType = "CONTAINS"
|
||||
|
||||
// ContainedByRelationship is to be used when SPDXRef-A is contained by SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c is contained by ARCHIVE file bar.tgz
|
||||
ContainedByRelationship RelationshipType = "CONTAINED_BY"
|
||||
|
||||
// DependsOnRelationship is to be used when SPDXRef-A depends on SPDXRef-B.
|
||||
// Example: Package A depends on the presence of package B in order to build and run
|
||||
DependsOnRelationship RelationshipType = "DEPENDS_ON"
|
||||
|
||||
// DependencyOfRelationship is to be used when SPDXRef-A is dependency of SPDXRef-B.
|
||||
// Example: A is explicitly stated as a dependency of B in a machine-readable file. Use when a package manager does not define scopes.
|
||||
DependencyOfRelationship RelationshipType = "DEPENDENCY_OF"
|
||||
|
||||
// DependencyManifestOfRelationship is to be used when SPDXRef-A is a manifest file that lists a set of dependencies for SPDXRef-B.
|
||||
// Example: A file package.json is the dependency manifest of a package foo. Note that only one manifest should be used to define the same dependency graph.
|
||||
DependencyManifestOfRelationship RelationshipType = "DEPENDENCY_MANIFEST_OF"
|
||||
|
||||
// BuildDependencyOfRelationship is to be used when SPDXRef-A is a build dependency of SPDXRef-B.
|
||||
// Example: A is in the compile scope of B in a Maven project.
|
||||
BuildDependencyOfRelationship RelationshipType = "BUILD_DEPENDENCY_OF"
|
||||
|
||||
// DevDependencyOfRelationship is to be used when SPDXRef-A is a development dependency of SPDXRef-B.
|
||||
// Example: A is in the devDependencies scope of B in a Maven project.
|
||||
DevDependencyOfRelationship RelationshipType = "DEV_DEPENDENCY_OF"
|
||||
|
||||
// OptionalDependencyOfRelationship is to be used when SPDXRef-A is an optional dependency of SPDXRef-B.
|
||||
// Example: Use when building the code will proceed even if a dependency cannot be found, fails to install, or is only installed on a specific platform. For example, A is in the optionalDependencies scope of npm project B.
|
||||
OptionalDependencyOfRelationship RelationshipType = "OPTIONAL_DEPENDENCY_OF"
|
||||
|
||||
// ProvidedDependencyOfRelationship is to be used when SPDXRef-A is a to be provided dependency of SPDXRef-B.
|
||||
// Example: A is in the provided scope of B in a Maven project, indicating that the project expects it to be provided, for instance, by the container or JDK.
|
||||
ProvidedDependencyOfRelationship RelationshipType = "PROVIDED_DEPENDENCY_OF"
|
||||
|
||||
// TestDependencyOfRelationship is to be used when SPDXRef-A is a test dependency of SPDXRef-B.
|
||||
// Example: A is in the test scope of B in a Maven project.
|
||||
TestDependencyOfRelationship RelationshipType = "TEST_DEPENDENCY_OF"
|
||||
|
||||
// RuntimeDependencyOfRelationship is to be used when SPDXRef-A is a dependency required for the execution of SPDXRef-B.
|
||||
// Example: A is in the runtime scope of B in a Maven project.
|
||||
RuntimeDependencyOfRelationship RelationshipType = "RUNTIME_DEPENDENCY_OF"
|
||||
|
||||
// ExampleOfRelationship is to be used when SPDXRef-A is an example of SPDXRef-B.
|
||||
// Example: The file or snippet that illustrates how to use an application or library.
|
||||
ExampleOfRelationship RelationshipType = "EXAMPLE_OF"
|
||||
|
||||
// GeneratesRelationship is to be used when SPDXRef-A generates SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk generates a BINARY file a.out
|
||||
GeneratesRelationship RelationshipType = "GENERATES"
|
||||
|
||||
// GeneratedFromRelationship is to be used when SPDXRef-A was generated from SPDXRef-B.
|
||||
// Example: A BINARY file a.out has been generated from a SOURCE file makefile.mk. A BINARY file foolib.a is generated from a SOURCE file bar.c.
|
||||
GeneratedFromRelationship RelationshipType = "GENERATED_FROM"
|
||||
|
||||
// AncestorOfRelationship is to be used when SPDXRef-A is an ancestor (same lineage but pre-dates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is a version of the original ancestor SOURCE file 'makefile2.mk'
|
||||
AncestorOfRelationship RelationshipType = "ANCESTOR_OF"
|
||||
|
||||
// DescendantOfRelationship is to be used when SPDXRef-A is a descendant of (same lineage but postdates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a descendant of the original SOURCE file 'makefile.mk'
|
||||
DescendantOfRelationship RelationshipType = "DESCENDANT_OF"
|
||||
|
||||
// VariantOfRelationship is to be used when SPDXRef-A is a variant of (same lineage but not clear which came first) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a variant of SOURCE file makefile.mk if they differ by some edit, but there is no way to tell which came first (no reliable date information).
|
||||
VariantOfRelationship RelationshipType = "VARIANT_OF"
|
||||
|
||||
// DistributionArtifactRelationship is to be used when distributing SPDXRef-A requires that SPDXRef-B also be distributed.
|
||||
// Example: A BINARY file foo.o requires that the ARCHIVE file bar-sources.tgz be made available on distribution.
|
||||
DistributionArtifactRelationship RelationshipType = "DISTRIBUTION_ARTIFACT"
|
||||
|
||||
// PatchForRelationship is to be used when SPDXRef-A is a patch file for (to be applied to) SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file for SOURCE file foo.c.
|
||||
PatchForRelationship RelationshipType = "PATCH_FOR"
|
||||
|
||||
// PatchAppliedRelationship is to be used when SPDXRef-A is a patch file that has been applied to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file that has been applied to SOURCE file 'foo-patched.c'.
|
||||
PatchAppliedRelationship RelationshipType = "PATCH_APPLIED"
|
||||
|
||||
// CopyOfRelationship is to be used when SPDXRef-A is an exact copy of SPDXRef-B.
|
||||
// Example: A BINARY file alib.a is an exact copy of BINARY file a2lib.a.
|
||||
CopyOfRelationship RelationshipType = "COPY_OF"
|
||||
|
||||
// FileAddedRelationship is to be used when SPDXRef-A is a file that was added to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been added to package ARCHIVE bar.tgz.
|
||||
FileAddedRelationship RelationshipType = "FILE_ADDED"
|
||||
|
||||
// FileDeletedRelationship is to be used when SPDXRef-A is a file that was deleted from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff has been deleted from package ARCHIVE bar.tgz.
|
||||
FileDeletedRelationship RelationshipType = "FILE_DELETED"
|
||||
|
||||
// FileModifiedRelationship is to be used when SPDXRef-A is a file that was modified from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been modified from SOURCE file foo.orig.c.
|
||||
FileModifiedRelationship RelationshipType = "FILE_MODIFIED"
|
||||
|
||||
// ExpandedFromArchiveRelationship is to be used when SPDXRef-A is expanded from the archive SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c, has been expanded from the archive ARCHIVE file xyz.tgz.
|
||||
ExpandedFromArchiveRelationship RelationshipType = "EXPANDED_FROM_ARCHIVE"
|
||||
|
||||
// DynamicLinkRelationship is to be used when SPDXRef-A dynamically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' dynamically links to BINARY file zlib.so.
|
||||
DynamicLinkRelationship RelationshipType = "DYNAMIC_LINK"
|
||||
|
||||
// StaticLinkRelationship is to be used when SPDXRef-A statically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' statically links to BINARY zlib.a.
|
||||
StaticLinkRelationship RelationshipType = "STATIC_LINK"
|
||||
|
||||
// DataFileOfRelationship is to be used when SPDXRef-A is a data file used in SPDXRef-B.
|
||||
// Example: An IMAGE file 'kitty.jpg' is a data file of an APPLICATION 'hellokitty'.
|
||||
DataFileOfRelationship RelationshipType = "DATA_FILE_OF"
|
||||
|
||||
// TestCaseOfRelationship is to be used when SPDXRef-A is a test case used in testing SPDXRef-B.
|
||||
// Example: A SOURCE file testMyCode.java is a unit test file used to test an APPLICATION MyPackage.
|
||||
TestCaseOfRelationship RelationshipType = "TEST_CASE_OF"
|
||||
|
||||
// BuildToolOfRelationship is to be used when SPDXRef-A is used to build SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is used to build an APPLICATION 'zlib'.
|
||||
BuildToolOfRelationship RelationshipType = "BUILD_TOOL_OF"
|
||||
|
||||
// DevToolOfRelationship is to be used when SPDXRef-A is used as a development tool for SPDXRef-B.
|
||||
// Example: Any tool used for development such as a code debugger.
|
||||
DevToolOfRelationship RelationshipType = "DEV_TOOL_OF"
|
||||
|
||||
// TestOfRelationship is to be used when SPDXRef-A is used for testing SPDXRef-B.
|
||||
// Example: Generic relationship for cases where it's clear that something is used for testing but unclear whether it's TEST_CASE_OF or TEST_TOOL_OF.
|
||||
TestOfRelationship RelationshipType = "TEST_OF"
|
||||
|
||||
// TestToolOfRelationship is to be used when SPDXRef-A is used as a test tool for SPDXRef-B.
|
||||
// Example: Any tool used to test the code such as ESlint.
|
||||
TestToolOfRelationship RelationshipType = "TEST_TOOL_OF"
|
||||
|
||||
// DocumentationOfRelationship is to be used when SPDXRef-A provides documentation of SPDXRef-B.
|
||||
// Example: A DOCUMENTATION file readme.txt documents the APPLICATION 'zlib'.
|
||||
DocumentationOfRelationship RelationshipType = "DOCUMENTATION_OF"
|
||||
|
||||
// OptionalComponentOfRelationship is to be used when SPDXRef-A is an optional component of SPDXRef-B.
|
||||
// Example: A SOURCE file fool.c (which is in the contributors directory) may or may not be included in the build of APPLICATION 'atthebar'.
|
||||
OptionalComponentOfRelationship RelationshipType = "OPTIONAL_COMPONENT_OF"
|
||||
|
||||
// MetafileOfRelationship is to be used when SPDXRef-A is a metafile of SPDXRef-B.
|
||||
// Example: A SOURCE file pom.xml is a metafile of the APPLICATION 'Apache Xerces'.
|
||||
MetafileOfRelationship RelationshipType = "METAFILE_OF"
|
||||
|
||||
// PackageOfRelationship is to be used when SPDXRef-A is used as a package as part of SPDXRef-B.
|
||||
// Example: A Linux distribution contains an APPLICATION package gawk as part of the distribution MyLinuxDistro.
|
||||
PackageOfRelationship RelationshipType = "PACKAGE_OF"
|
||||
|
||||
// AmendsRelationship is to be used when (current) SPDXRef-DOCUMENT amends the SPDX information in SPDXRef-B.
|
||||
// Example: (Current) SPDX document A version 2 contains a correction to a previous version of the SPDX document A version 1. Note the reserved identifier SPDXRef-DOCUMENT for the current document is required.
|
||||
AmendsRelationship RelationshipType = "AMENDS"
|
||||
|
||||
// PrerequisiteForRelationship is to be used when SPDXRef-A is a prerequisite for SPDXRef-B.
|
||||
// Example: A library bar.dll is a prerequisite or dependency for APPLICATION foo.exe
|
||||
PrerequisiteForRelationship RelationshipType = "PREREQUISITE_FOR"
|
||||
|
||||
// HasPrerequisiteRelationship is to be used when SPDXRef-A has as a prerequisite SPDXRef-B.
|
||||
// Example: An APPLICATION foo.exe has prerequisite or dependency on bar.dll
|
||||
HasPrerequisiteRelationship RelationshipType = "HAS_PREREQUISITE"
|
||||
|
||||
// OtherRelationship is to be used for a relationship which has not been defined in the formal SPDX specification. A description of the relationship should be included in the Relationship comments field.
|
||||
OtherRelationship RelationshipType = "OTHER"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "alpine-3.10",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-01-20T21:40:24.439211Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.15"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/alpine-3.10-204b304b-beb3-4413-9b38-d8a2e58e3dfb",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-a61243292e73923",
|
||||
"name": "busybox",
|
||||
"licenseConcluded": "GPL-2.0",
|
||||
"description": "Size optimized toolbox of many common UNIX utilities",
|
||||
"downloadLocation": "https://busybox.net/",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:busybox:busybox:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/busybox@1.30.1-r5?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "GPL-2.0",
|
||||
"originator": "Person: Natanael Copa <ncopa@alpinelinux.org>",
|
||||
"sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
|
||||
"versionInfo": "1.30.1-r5"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-d2f55e316dbe92e4",
|
||||
"name": "libssl1.1",
|
||||
"licenseConcluded": "OpenSSL",
|
||||
"description": "SSL shared libraries",
|
||||
"downloadLocation": "https://www.openssl.org",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:libssl1.1:libssl1.1:1.1.1k-r0:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/libssl1.1@1.1.1k-r0?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "OpenSSL",
|
||||
"originator": "Person: Timo Teras <timo.teras@iki.fi>",
|
||||
"sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
|
||||
"versionInfo": "1.1.1k-r0"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-2b24657ad7aaafea",
|
||||
"name": "ssl_client",
|
||||
"licenseConcluded": "GPL-2.0",
|
||||
"description": "EXternal ssl_client for busybox wget",
|
||||
"downloadLocation": "https://busybox.net/",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl-client:ssl-client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl-client:ssl_client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl_client:ssl-client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl_client:ssl_client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl:ssl-client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:a:ssl:ssl_client:1.30.1-r5:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/ssl_client@1.30.1-r5?arch=x86_64&upstream=busybox&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "GPL-2.0",
|
||||
"originator": "Person: Natanael Copa <ncopa@alpinelinux.org>",
|
||||
"sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
|
||||
"versionInfo": "1.30.1-r5"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-a07392483a2d0750",
|
||||
"comment": "layerID: sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"fileName": "/bin/busybox"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-aa3cfed221706d80",
|
||||
"comment": "layerID: sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"fileName": "/lib/libssl.so.1.1"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-a07392483a2d0750"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-a07392483a2d0750"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-499bb68237b0f2b8"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-df78c68c8206be69"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-7c980486fc17af43"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-a61243292e73923",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-8762661e65166719"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-d2f55e316dbe92e4",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-aa3cfed221706d80"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-d2f55e316dbe92e4",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-aa3cfed221706d80"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-01-18T22:16:16Z",
|
||||
"creators": [
|
||||
"Tool: vendor"
|
||||
],
|
||||
"licenseListVersion": "3.8"
|
||||
},
|
||||
"name": "alpine:3.10",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://spdx.org/spdxdocs/alpine-154c794c-4264-4e9f-a2ff-5c01bfbfc02c",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-alpine-3.10"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "alpine",
|
||||
"SPDXID": "SPDXRef-alpine-3.10",
|
||||
"versionInfo": "3.10",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
},
|
||||
{
|
||||
"name": "busybox",
|
||||
"SPDXID": "SPDXRef-busybox-1.30.1-r5",
|
||||
"versionInfo": "1.30.1-r5",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "LicenseRef-7d19a72",
|
||||
"copyrightText": "NONE",
|
||||
"comment": "busybox:\n\twarning: No metadata for key: copyright\n\twarning: No metadata for key: download_url\n\twarning: No metadata for key: checksum\n\twarning: No metadata for key: pkg_licenses\n\twarning: No metadata for key: pkg_format\n\twarning: No metadata for key: src_name\n\twarning: No metadata for key: src_version\n"
|
||||
},
|
||||
{
|
||||
"name": "ssl_client",
|
||||
"SPDXID": "SPDXRef-ssl_client-1.30.1-r5",
|
||||
"versionInfo": "1.30.1-r5",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "LicenseRef-de5acdd",
|
||||
"copyrightText": "NONE",
|
||||
"comment": "ssl_client:\n\twarning: No metadata for key: copyright\n\twarning: No metadata for key: download_url\n\twarning: No metadata for key: checksum\n\twarning: No metadata for key: pkg_licenses\n\twarning: No metadata for key: pkg_format\n\twarning: No metadata for key: src_name\n\twarning: No metadata for key: src_version\n"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relatedSpdxElement": "SPDXRef-alpine-3.10",
|
||||
"relationshipType": "DESCRIBES"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-9fb3aa2f8b",
|
||||
"relatedSpdxElement": "SPDXRef-busybox-1.30.1-r5",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-9fb3aa2f8b",
|
||||
"relatedSpdxElement": "SPDXRef-ssl_client-1.30.1-r5",
|
||||
"relationshipType": "CONTAINS"
|
||||
}
|
||||
],
|
||||
"hasExtractedLicensingInfos": [
|
||||
{
|
||||
"extractedText": "OpenSSL",
|
||||
"licenseId": "LicenseRef-de5acdd"
|
||||
},
|
||||
{
|
||||
"extractedText": "GPL-2.0",
|
||||
"licenseId": "LicenseRef-7d19a72"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [
|
||||
{
|
||||
"Person": "Nisha K (nishak@vmware.com)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "hello-go-binary.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-go-binary",
|
||||
"externalDocumentRefs": [
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-hello-go-module",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "d661f8f831a99c288a64e5843b4794ad5181224a"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/hello-go-module-cfa0c58d-79db-4860-99b6-258477e4838b"
|
||||
},
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-golang-dist",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "b6cf54a46329e7cc7610aa5d244018b80103d111"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/golang-dist-492dfde4-318b-49f7-b48c-934bfafbde48"
|
||||
},
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-hello-imports",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "14ff98203c3ddd2bd4803c00b5225d2551ca603c"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/hello-imports-c2d068df-67aa-4c68-98c8-100b450fc408"
|
||||
}
|
||||
],
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-bin-hello"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"packageName": "hello",
|
||||
"SPDXID": "SPDXRef-go-bin-hello",
|
||||
"downloadLocation": "git@github.com:swinslow/spdx-examples.git#example7/content/build/hello",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "DocumentRef-golang-dist",
|
||||
"relatedSpdxElement": "DocumentRef-hello-go-module",
|
||||
"relationshipType": "BUILD_TOOL_OF"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "DocumentRef-golang-dist:SPDXRef-go-compiler",
|
||||
"relatedSpdxElement": "SPDXRef-go-bin-hello",
|
||||
"relationshipType": "GENERATES"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "DocumentRef-hello-imports",
|
||||
"relatedSpdxElement": "SPDXRef-go-bin-hello",
|
||||
"relationshipType": "STATIC_LINK"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [
|
||||
{
|
||||
"Person": "Nisha K (nishak@vmware.com)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "hello-go-module.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-go-module",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-module-example.com/hello"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"packageName": "example.com/hello",
|
||||
"SPDXID": "SPDXRef-go-module-example.com/hello",
|
||||
"downloadLocation": "git@github.com:swinslow/spdx-examples.git#example7/content/src/hello",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [
|
||||
{
|
||||
"Person": "Nisha K (nishak@vmware.com)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "golang-dist",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/golang-dist",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-golang-dist"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"packageName": "go1.16.4.linux-amd64",
|
||||
"SPDXID": "SPDXRef-golang-dist",
|
||||
"downloadLocation": "https://golang.org/dl/go1.16.4.linux-amd64.tar.gz",
|
||||
"packageVersion": "1.16.4",
|
||||
"filesAnalyzed": "false",
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA256",
|
||||
"checksumValue": "7154e88f5a8047aad4b80ebace58a059e36e7e2e4eb3b383127a28c711b4ff59"
|
||||
}
|
||||
],
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "LicenseRef-Golang-BSD-plus-Patents",
|
||||
"packageCopyrightText": "Copyright (c) 2009 The Go Authors. All rights reserved."
|
||||
},
|
||||
{
|
||||
"packageName": "go",
|
||||
"SPDXID": "SPDXRef-go-compiler",
|
||||
"downloadLocation": "https://golang.org/dl/go1.16.4.linux-amd64.tar.gz",
|
||||
"packageVersion": "1.16.4",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [
|
||||
{
|
||||
"Person": "Nisha K (nishak@vmware.com)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "hello-imports.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-imports",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-module-golang.org/x/text",
|
||||
"SPDXRef-go-module-rsc.io/quote",
|
||||
"SPDXRef-go-module-rsc.io/sampler"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"packageName": "golang.org/x/text",
|
||||
"SPDXID": "SPDXRef-go-module-golang.org/x/text",
|
||||
"downloadLocation": "go://golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
},
|
||||
{
|
||||
"packageName": "rsc.io/quote",
|
||||
"SPDXID": "SPDXRef-go-module-rsc.io/quote",
|
||||
"downloadLocation": "go://rsc.io/quote@v1.5.2",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
},
|
||||
{
|
||||
"packageName": "rsc.io/sampler",
|
||||
"SPDXID": "SPDXRef-go-module-rsc.io/sampler",
|
||||
"downloadLocation": "go://rsc.io/sampler@v1.3.0",
|
||||
"filesAnalyzed": "false",
|
||||
"packageLicenseConcluded": "NOASSERTION",
|
||||
"packageLicenseDeclared": "NOASSERTION",
|
||||
"packageCopyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [ "Person: Nisha K (nishak@vmware.com)" ]
|
||||
},
|
||||
"name": "hello-go-binary.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-go-binary",
|
||||
"externalDocumentRefs": [
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-hello-go-module",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "d661f8f831a99c288a64e5843b4794ad5181224a"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/hello-go-module-cfa0c58d-79db-4860-99b6-258477e4838b"
|
||||
},
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-golang-dist",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "b6cf54a46329e7cc7610aa5d244018b80103d111"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/golang-dist-492dfde4-318b-49f7-b48c-934bfafbde48"
|
||||
},
|
||||
{
|
||||
"externalDocumentId": "DocumentRef-hello-imports",
|
||||
"checksum": {
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "14ff98203c3ddd2bd4803c00b5225d2551ca603c"
|
||||
},
|
||||
"spdxDocument": "https://swinslow.net/spdx-examples/example7/hello-imports-c2d068df-67aa-4c68-98c8-100b450fc408"
|
||||
}
|
||||
],
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-bin-hello"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "hello",
|
||||
"SPDXID": "SPDXRef-go-bin-hello",
|
||||
"downloadLocation": "git@github.com:swinslow/spdx-examples.git#example7/content/build/hello",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "DocumentRef-golang-dist:SPDXRef-golang-dist",
|
||||
"relatedSpdxElement": "DocumentRef-hello-go-module:SPDXRef-hello-go-module",
|
||||
"relationshipType": "BUILD_TOOL_OF"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "DocumentRef-golang-dist:SPDXRef-go-compiler",
|
||||
"relatedSpdxElement": "SPDXRef-go-bin-hello",
|
||||
"relationshipType": "GENERATES"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "DocumentRef-hello-imports:SPDXRef-hello-imports",
|
||||
"relatedSpdxElement": "SPDXRef-go-bin-hello",
|
||||
"relationshipType": "STATIC_LINK"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [ "Person: Nisha K (nishak@vmware.com)" ]
|
||||
},
|
||||
"name": "hello-go-module.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-go-module",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-module-example.com/hello"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "example.com/hello",
|
||||
"SPDXID": "SPDXRef-go-module-example.com/hello",
|
||||
"downloadLocation": "git@github.com:swinslow/spdx-examples.git#example7/content/src/hello",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [ "Person: Nisha K (nishak@vmware.com)" ]
|
||||
},
|
||||
"name": "golang-dist",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/golang-dist",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-golang-dist"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "go1.16.4.linux-amd64",
|
||||
"SPDXID": "SPDXRef-golang-dist",
|
||||
"downloadLocation": "https://golang.org/dl/go1.16.4.linux-amd64.tar.gz",
|
||||
"versionInfo": "1.16.4",
|
||||
"filesAnalyzed": false,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA256",
|
||||
"checksumValue": "7154e88f5a8047aad4b80ebace58a059e36e7e2e4eb3b383127a28c711b4ff59"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "LicenseRef-Golang-BSD-plus-Patents",
|
||||
"copyrightText": "Copyright (c) 2009 The Go Authors. All rights reserved."
|
||||
},
|
||||
{
|
||||
"name": "go",
|
||||
"SPDXID": "SPDXRef-go-compiler",
|
||||
"downloadLocation": "https://golang.org/dl/go1.16.4.linux-amd64.tar.gz",
|
||||
"versionInfo": "1.16.4",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2020-11-24T01:12:27Z",
|
||||
"creators": [ "Person: Nisha K (nishak@vmware.com)" ]
|
||||
},
|
||||
"name": "hello-imports.spdx.json",
|
||||
"documentNamespace": "https://swinslow.net/spdx-examples/example7/hello-imports",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-go-module-golang.org/x/text",
|
||||
"SPDXRef-go-module-rsc.io/quote",
|
||||
"SPDXRef-go-module-rsc.io/sampler"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "golang.org/x/text",
|
||||
"SPDXID": "SPDXRef-go-module-golang.org/x/text",
|
||||
"downloadLocation": "go://golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
},
|
||||
{
|
||||
"name": "rsc.io/quote",
|
||||
"SPDXID": "SPDXRef-go-module-rsc.io/quote",
|
||||
"downloadLocation": "go://rsc.io/quote@v1.5.2",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
},
|
||||
{
|
||||
"name": "rsc.io/sampler",
|
||||
"SPDXID": "SPDXRef-go-module-rsc.io/sampler",
|
||||
"downloadLocation": "go://rsc.io/sampler@v1.3.0",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -167,28 +167,28 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
mimeTypePrefix := strings.Split(metadata.MIMEType, "/")[0]
|
||||
switch mimeTypePrefix {
|
||||
case "image":
|
||||
ty = append(ty, string(model.ImageFileType))
|
||||
ty = append(ty, string(spdxhelpers.ImageFileType))
|
||||
case "video":
|
||||
ty = append(ty, string(model.VideoFileType))
|
||||
ty = append(ty, string(spdxhelpers.VideoFileType))
|
||||
case "application":
|
||||
ty = append(ty, string(model.ApplicationFileType))
|
||||
ty = append(ty, string(spdxhelpers.ApplicationFileType))
|
||||
case "text":
|
||||
ty = append(ty, string(model.TextFileType))
|
||||
ty = append(ty, string(spdxhelpers.TextFileType))
|
||||
case "audio":
|
||||
ty = append(ty, string(model.AudioFileType))
|
||||
ty = append(ty, string(spdxhelpers.AudioFileType))
|
||||
}
|
||||
|
||||
if internal.IsExecutable(metadata.MIMEType) {
|
||||
ty = append(ty, string(model.BinaryFileType))
|
||||
ty = append(ty, string(spdxhelpers.BinaryFileType))
|
||||
}
|
||||
|
||||
if internal.IsArchive(metadata.MIMEType) {
|
||||
ty = append(ty, string(model.ArchiveFileType))
|
||||
ty = append(ty, string(spdxhelpers.ArchiveFileType))
|
||||
}
|
||||
|
||||
// TODO: add support for source, spdx, and documentation file types
|
||||
if len(ty) == 0 {
|
||||
ty = append(ty, string(model.OtherFileType))
|
||||
ty = append(ty, string(spdxhelpers.OtherFileType))
|
||||
}
|
||||
|
||||
return ty
|
||||
@@ -213,12 +213,12 @@ func toRelationships(relationships []artifact.Relationship) (result []model.Rela
|
||||
return result
|
||||
}
|
||||
|
||||
func lookupRelationship(ty artifact.RelationshipType) (bool, model.RelationshipType, string) {
|
||||
func lookupRelationship(ty artifact.RelationshipType) (bool, spdxhelpers.RelationshipType, string) {
|
||||
switch ty {
|
||||
case artifact.ContainsRelationship:
|
||||
return true, model.ContainsRelationship, ""
|
||||
return true, spdxhelpers.ContainsRelationship, ""
|
||||
case artifact.OwnershipByFileOverlapRelationship:
|
||||
return true, model.OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
||||
return true, spdxhelpers.OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -27,7 +28,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "application/vnd.unknown",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.ApplicationFileType),
|
||||
string(spdxhelpers.ApplicationFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -36,8 +37,8 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "application/zip",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.ApplicationFileType),
|
||||
string(model.ArchiveFileType),
|
||||
string(spdxhelpers.ApplicationFileType),
|
||||
string(spdxhelpers.ArchiveFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -46,7 +47,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "audio/ogg",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.AudioFileType),
|
||||
string(spdxhelpers.AudioFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -55,7 +56,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "video/3gpp",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.VideoFileType),
|
||||
string(spdxhelpers.VideoFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -64,7 +65,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "text/html",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.TextFileType),
|
||||
string(spdxhelpers.TextFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -73,7 +74,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "image/png",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.ImageFileType),
|
||||
string(spdxhelpers.ImageFileType),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -82,8 +83,8 @@ func Test_toFileTypes(t *testing.T) {
|
||||
MIMEType: "application/x-sharedlib",
|
||||
},
|
||||
expected: []string{
|
||||
string(model.ApplicationFileType),
|
||||
string(model.BinaryFileType),
|
||||
string(spdxhelpers.ApplicationFileType),
|
||||
string(spdxhelpers.BinaryFileType),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -99,18 +100,18 @@ func Test_lookupRelationship(t *testing.T) {
|
||||
tests := []struct {
|
||||
input artifact.RelationshipType
|
||||
exists bool
|
||||
ty model.RelationshipType
|
||||
ty spdxhelpers.RelationshipType
|
||||
comment string
|
||||
}{
|
||||
{
|
||||
input: artifact.ContainsRelationship,
|
||||
exists: true,
|
||||
ty: model.ContainsRelationship,
|
||||
ty: spdxhelpers.ContainsRelationship,
|
||||
},
|
||||
{
|
||||
input: artifact.OwnershipByFileOverlapRelationship,
|
||||
exists: true,
|
||||
ty: model.OtherRelationship,
|
||||
ty: spdxhelpers.OtherRelationship,
|
||||
comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func validator(reader io.Reader) error {
|
||||
_, err := decoder(reader)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package spdx22tagvalue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spdx/tools-golang/tvloader"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||
doc, err := tvloader.Load2_2(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||
}
|
||||
|
||||
return spdxhelpers.ToSyftModel(doc)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ func Format() format.Format {
|
||||
return format.NewFormat(
|
||||
format.SPDXTagValueOption,
|
||||
encoder,
|
||||
nil,
|
||||
nil,
|
||||
decoder,
|
||||
validator,
|
||||
)
|
||||
}
|
||||
|
||||
+5
-5
@@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-22f5732a-cab0-4376-9b79-15e413049500
|
||||
LicenseListVersion: 3.15
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-4b90f56d-d596-4ad8-b6a5-17f7d801350d
|
||||
LicenseListVersion: 3.16
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2021-12-01T15:08:43Z
|
||||
Created: 2022-02-10T21:09:27Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-ad3d1c4abd84bf75
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-1d97af55efe9512f
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
||||
+5
-5
@@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-ce4d4ae5-9d79-4f84-a410-361e394c2908
|
||||
LicenseListVersion: 3.15
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-26a2def6-53d0-4504-b99a-a046832508ac
|
||||
LicenseListVersion: 3.16
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2021-12-01T15:08:44Z
|
||||
Created: 2022-02-10T21:09:27Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-73f796c846875b9e
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-d9527e708c11f8b9
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
||||
@@ -94,9 +94,9 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
|
||||
func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2 {
|
||||
results := make(map[spdx.ElementID]*spdx.Package2_2)
|
||||
|
||||
for p := range catalog.Enumerate() {
|
||||
for _, p := range catalog.Sorted() {
|
||||
// name should be guaranteed to be unique, but semantically useful and stable
|
||||
id := fmt.Sprintf("Package-%+v-%s", p.Type, p.Name)
|
||||
id := 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
|
||||
@@ -183,9 +183,11 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
|
||||
|
||||
// note: based on the purpose above no discovered checksums should be provided, but instead, only
|
||||
// tool-derived checksums.
|
||||
PackageChecksumSHA1: "",
|
||||
PackageChecksumSHA256: "",
|
||||
PackageChecksumMD5: "",
|
||||
//FIXME: this got removed between 0.1.0 and 0.2.0, is this right? it looks like
|
||||
// it wasn't being used anyway
|
||||
//PackageChecksumSHA1: "",
|
||||
//PackageChecksumSHA256: "",
|
||||
//PackageChecksumMD5: "",
|
||||
|
||||
// 3.11: Package Home Page
|
||||
// Cardinality: optional, one
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package spdx22tagvalue
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func validator(reader io.Reader) error {
|
||||
_, err := decoder(reader)
|
||||
return err
|
||||
}
|
||||
@@ -21,6 +21,10 @@ func TestEncodeDecodeCycle(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
}
|
||||
|
||||
|
||||
@@ -217,9 +217,17 @@ func toRelationshipModel(relationships []artifact.Relationship) []model.Relation
|
||||
func toSourceModel(src source.Metadata) (model.Source, error) {
|
||||
switch src.Scheme {
|
||||
case source.ImageScheme:
|
||||
metadata := src.ImageMetadata
|
||||
// ensure that empty collections are not shown as null
|
||||
if metadata.RepoDigests == nil {
|
||||
metadata.RepoDigests = []string{}
|
||||
}
|
||||
if metadata.Tags == nil {
|
||||
metadata.Tags = []string{}
|
||||
}
|
||||
return model.Source{
|
||||
Type: "image",
|
||||
Target: src.ImageMetadata,
|
||||
Target: metadata,
|
||||
}, nil
|
||||
case source.DirectoryScheme:
|
||||
return model.Source{
|
||||
|
||||
@@ -63,6 +63,8 @@ func Test_toSourceModel(t *testing.T) {
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
MediaType: "type...",
|
||||
RepoDigests: []string{},
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ package syftjson
|
||||
import (
|
||||
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -11,13 +12,16 @@ import (
|
||||
)
|
||||
|
||||
func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
catalog := toSyftCatalog(doc.Artifacts)
|
||||
|
||||
return &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: toSyftCatalog(doc.Artifacts),
|
||||
PackageCatalog: catalog,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
Descriptor: toSyftDescriptor(doc.Descriptor),
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
Descriptor: toSyftDescriptor(doc.Descriptor),
|
||||
Relationships: toSyftRelationships(&doc, catalog, doc.ArtifactRelationships),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -42,6 +46,59 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftRelationships(doc *model.Document, catalog *pkg.Catalog, relationships []model.Relationship) []artifact.Relationship {
|
||||
idMap := make(map[string]interface{})
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
idMap[string(p.ID())] = p
|
||||
for _, l := range p.Locations {
|
||||
idMap[string(l.Coordinates.ID())] = l.Coordinates
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range doc.Files {
|
||||
idMap[f.ID] = f.Location
|
||||
}
|
||||
|
||||
var out []artifact.Relationship
|
||||
for _, r := range relationships {
|
||||
syftRelationship := toSyftRelationship(idMap, r)
|
||||
if syftRelationship != nil {
|
||||
out = append(out, *syftRelationship)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toSyftRelationship(idMap map[string]interface{}, relationship model.Relationship) *artifact.Relationship {
|
||||
from, ok := idMap[relationship.Parent].(artifact.Identifiable)
|
||||
if !ok {
|
||||
log.Warnf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent])
|
||||
return nil
|
||||
}
|
||||
to, ok := idMap[relationship.Child].(artifact.Identifiable)
|
||||
if !ok {
|
||||
log.Warnf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child])
|
||||
return nil
|
||||
}
|
||||
typ := artifact.RelationshipType(relationship.Type)
|
||||
|
||||
switch typ {
|
||||
case artifact.OwnershipByFileOverlapRelationship:
|
||||
fallthrough
|
||||
case artifact.ContainsRelationship:
|
||||
default:
|
||||
log.Warnf("unknown relationship type: %s", typ)
|
||||
return nil
|
||||
}
|
||||
return &artifact.Relationship{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: typ,
|
||||
Data: relationship.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftDescriptor(d model.Descriptor) sbom.Descriptor {
|
||||
return sbom.Descriptor{
|
||||
Name: d.Name,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2021-11-15 14:32:22.081911 -0500 EST m=+0.159302107
|
||||
// This file was generated by robots at 2022-02-07 09:34:56.622482 -0500 EST m=+0.351558414
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.15"
|
||||
const Version = "3.16"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -64,6 +64,7 @@ var licenseIDs = map[string]string{
|
||||
"apl-1": "APL-1.0",
|
||||
"apl-1.0": "APL-1.0",
|
||||
"apl-1.0.0": "APL-1.0",
|
||||
"app-s2p": "App-s2p",
|
||||
"apsl-1": "APSL-1.0",
|
||||
"apsl-1.0": "APSL-1.0",
|
||||
"apsl-1.0.0": "APSL-1.0",
|
||||
@@ -399,6 +400,9 @@ var licenseIDs = map[string]string{
|
||||
"d-fsl-1.0": "D-FSL-1.0",
|
||||
"d-fsl-1.0.0": "D-FSL-1.0",
|
||||
"diffmark": "diffmark",
|
||||
"dl-de-by-2": "DL-DE-BY-2.0",
|
||||
"dl-de-by-2.0": "DL-DE-BY-2.0",
|
||||
"dl-de-by-2.0.0": "DL-DE-BY-2.0",
|
||||
"doc": "DOC",
|
||||
"dotseqn": "Dotseqn",
|
||||
"drl-1": "DRL-1.0",
|
||||
@@ -422,6 +426,9 @@ var licenseIDs = map[string]string{
|
||||
"efl-2.0": "EFL-2.0",
|
||||
"efl-2.0.0": "EFL-2.0",
|
||||
"egenix": "eGenix",
|
||||
"elastic-2": "Elastic-2.0",
|
||||
"elastic-2.0": "Elastic-2.0",
|
||||
"elastic-2.0.0": "Elastic-2.0",
|
||||
"entessa": "Entessa",
|
||||
"epics": "EPICS",
|
||||
"epl-1": "EPL-1.0",
|
||||
@@ -596,6 +603,7 @@ var licenseIDs = map[string]string{
|
||||
"ipl-1.0": "IPL-1.0",
|
||||
"ipl-1.0.0": "IPL-1.0",
|
||||
"isc": "ISC",
|
||||
"jam": "Jam",
|
||||
"jasper-2": "JasPer-2.0",
|
||||
"jasper-2.0": "JasPer-2.0",
|
||||
"jasper-2.0.0": "JasPer-2.0",
|
||||
@@ -909,6 +917,7 @@ var licenseIDs = map[string]string{
|
||||
"sax-pd": "SAX-PD",
|
||||
"saxpath": "Saxpath",
|
||||
"scea": "SCEA",
|
||||
"schemereport": "SchemeReport",
|
||||
"sendmail": "Sendmail",
|
||||
"sendmail-8": "Sendmail-8.23",
|
||||
"sendmail-8.23": "Sendmail-8.23",
|
||||
@@ -1007,6 +1016,7 @@ var licenseIDs = map[string]string{
|
||||
"wtfpl": "WTFPL",
|
||||
"wxwindows": "wxWindows",
|
||||
"x11": "X11",
|
||||
"x11-distribute-modifications-variant": "X11-distribute-modifications-variant",
|
||||
"xerox": "Xerox",
|
||||
"xfree86-1": "XFree86-1.1",
|
||||
"xfree86-1.1": "XFree86-1.1",
|
||||
|
||||
@@ -50,11 +50,11 @@ type ApkFileRecord struct {
|
||||
// PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec)
|
||||
func (m ApkMetadata) PackageURL(distro *linux.Release) string {
|
||||
qualifiers := map[string]string{
|
||||
purlArchQualifier: m.Architecture,
|
||||
PURLQualifierArch: m.Architecture,
|
||||
}
|
||||
|
||||
if m.OriginPackage != "" {
|
||||
qualifiers[purlUpstreamQualifier] = m.OriginPackage
|
||||
qualifiers[PURLQualifierUpstream] = m.OriginPackage
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
|
||||
@@ -40,11 +40,11 @@ var (
|
||||
)
|
||||
|
||||
func candidateProductsForJava(p pkg.Package) []string {
|
||||
return productsFromArtifactAndGroupIDs(artifactIDFromJavaPackage(p), groupIDsFromJavaPackage(p))
|
||||
return productsFromArtifactAndGroupIDs(artifactIDFromJavaPackage(p), GroupIDsFromJavaPackage(p))
|
||||
}
|
||||
|
||||
func candidateVendorsForJava(p pkg.Package) fieldCandidateSet {
|
||||
gidVendors := vendorsFromGroupIDs(groupIDsFromJavaPackage(p))
|
||||
gidVendors := vendorsFromGroupIDs(GroupIDsFromJavaPackage(p))
|
||||
nameVendors := vendorsFromJavaManifestNames(p)
|
||||
return newFieldCandidateSetFromSets(gidVendors, nameVendors)
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
|
||||
return artifactID
|
||||
}
|
||||
|
||||
func groupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
|
||||
@@ -333,7 +333,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expects, groupIDsFromJavaPackage(test.pkg))
|
||||
assert.ElementsMatch(t, test.expects, GroupIDsFromJavaPackage(test.pkg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,28 +55,13 @@ type archiveFilename struct {
|
||||
version string
|
||||
}
|
||||
|
||||
// TODO: Remove this method once we're using Go 1.15+.
|
||||
//
|
||||
// Go 1.15 introduces a `SubexpIndex` method for the Regexp type that would let
|
||||
// this code be made more elegant. Once we've reached 1.15, we should eliminate
|
||||
// this function in favor of that method.
|
||||
func subexpIndex(re *regexp.Regexp, name string) int {
|
||||
for i, subexpName := range re.SubexpNames() {
|
||||
if subexpName == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func getSubexp(matches []string, subexpName string, re *regexp.Regexp, raw string) string {
|
||||
if len(matches) < 1 {
|
||||
log.Warnf("unexpectedly empty matches for archive '%s'", raw)
|
||||
return ""
|
||||
}
|
||||
|
||||
index := subexpIndex(re, subexpName)
|
||||
index := re.SubexpIndex(subexpName)
|
||||
if index < 1 {
|
||||
log.Warnf("unexpected index of '%s' capture group for Java archive '%s'", subexpName, raw)
|
||||
return ""
|
||||
|
||||
@@ -122,6 +122,13 @@ func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error)
|
||||
pkgs = append([]*pkg.Package{parentPkg}, pkgs...)
|
||||
}
|
||||
|
||||
// add pURLs to all packages found
|
||||
// note: since package information may change after initial creation when parsing multiple locations within the
|
||||
// jar, we wait until the conclusion of the parsing process before synthesizing pURLs.
|
||||
for _, p := range pkgs {
|
||||
addPURL(p)
|
||||
}
|
||||
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
@@ -348,7 +355,7 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po
|
||||
}
|
||||
|
||||
if packageIdentitiesMatch(p, parentPkg) {
|
||||
updatePackage(p, parentPkg)
|
||||
updateParentPackage(p, parentPkg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -379,7 +386,7 @@ func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func updatePackage(p pkg.Package, parentPkg *pkg.Package) {
|
||||
func updateParentPackage(p pkg.Package, parentPkg *pkg.Package) {
|
||||
// we've run across more information about our parent package, add this info to the parent package metadata
|
||||
// the pom properties is typically a better source of information for name and version than the manifest
|
||||
parentPkg.Name = p.Name
|
||||
@@ -401,3 +408,17 @@ func updatePackage(p pkg.Package, parentPkg *pkg.Package) {
|
||||
parentPkg.Metadata = parentMetadata
|
||||
}
|
||||
}
|
||||
|
||||
func addPURL(p *pkg.Package) {
|
||||
purl := packageURL(*p)
|
||||
if purl == "" {
|
||||
return
|
||||
}
|
||||
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
metadata.PURL = purl
|
||||
p.Metadata = metadata
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ func TestParseJar(t *testing.T) {
|
||||
Version: "1.0-SNAPSHOT",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -154,6 +155,7 @@ func TestParseJar(t *testing.T) {
|
||||
"Manifest-Version": "1.0",
|
||||
},
|
||||
},
|
||||
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -191,6 +193,7 @@ func TestParseJar(t *testing.T) {
|
||||
Version: "0.1.0",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
},
|
||||
},
|
||||
"joda-time": {
|
||||
@@ -219,6 +222,7 @@ func TestParseJar(t *testing.T) {
|
||||
Description: "Date and time library to replace JDK date handling",
|
||||
URL: "http://www.joda.org/joda-time/",
|
||||
},
|
||||
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
|
||||
)
|
||||
|
||||
// PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(p pkg.Package) string {
|
||||
var groupID = p.Name
|
||||
groupIDs := cpe.GroupIDsFromJavaPackage(p)
|
||||
if len(groupIDs) > 0 {
|
||||
groupID = groupIDs[0]
|
||||
}
|
||||
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeMaven, // TODO: should we filter down by package types here?
|
||||
groupID,
|
||||
p.Name,
|
||||
p.Version,
|
||||
nil, // TODO: there are probably several qualifiers that can be specified here
|
||||
"")
|
||||
return pURL.ToString()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
pkg pkg.Package
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
pkg: pkg.Package{
|
||||
Name: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Manifest-Version": "1.0",
|
||||
},
|
||||
},
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties",
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
Extra: make(map[string]string),
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expect, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expect, packageURL(tt.pkg))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -46,14 +46,14 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
|
||||
}
|
||||
|
||||
qualifiers := map[string]string{
|
||||
purlArchQualifier: m.Architecture,
|
||||
PURLQualifierArch: m.Architecture,
|
||||
}
|
||||
|
||||
if m.Source != "" {
|
||||
if m.SourceVersion != "" {
|
||||
qualifiers[purlUpstreamQualifier] = fmt.Sprintf("%s@%s", m.Source, m.SourceVersion)
|
||||
qualifiers[PURLQualifierUpstream] = fmt.Sprintf("%s@%s", m.Source, m.SourceVersion)
|
||||
} else {
|
||||
qualifiers[purlUpstreamQualifier] = m.Source
|
||||
qualifiers[PURLQualifierUpstream] = m.Source
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import (
|
||||
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
var _ urlIdentifier = (*JavaMetadata)(nil)
|
||||
|
||||
var JenkinsPluginPomPropertiesGroupIDs = []string{
|
||||
var jenkinsPluginPomPropertiesGroupIDs = []string{
|
||||
"io.jenkins.plugins",
|
||||
"org.jenkins.plugins",
|
||||
"org.jenkins-ci.plugins",
|
||||
@@ -25,6 +24,7 @@ type JavaMetadata struct {
|
||||
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).
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ type PomParent struct {
|
||||
|
||||
// PkgTypeIndicated returns the package Type indicated by the data contained in the PomProperties.
|
||||
func (p PomProperties) PkgTypeIndicated() Type {
|
||||
if internal.HasAnyOfPrefixes(p.GroupID, JenkinsPluginPomPropertiesGroupIDs...) || strings.Contains(p.GroupID, ".jenkins.plugin") {
|
||||
if internal.HasAnyOfPrefixes(p.GroupID, jenkinsPluginPomPropertiesGroupIDs...) || strings.Contains(p.GroupID, ".jenkins.plugin") {
|
||||
return JenkinsPluginPkg
|
||||
}
|
||||
|
||||
@@ -74,18 +74,5 @@ type JavaManifest struct {
|
||||
|
||||
// PackageURL returns the PURL for the specific Maven package (see https://github.com/package-url/purl-spec)
|
||||
func (m JavaMetadata) PackageURL(_ *linux.Release) string {
|
||||
if m.PomProperties != nil {
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeMaven,
|
||||
m.PomProperties.GroupID,
|
||||
m.PomProperties.ArtifactID,
|
||||
m.PomProperties.Version,
|
||||
nil, // TODO: there are probably several qualifiers that can be specified here
|
||||
"")
|
||||
return pURL.ToString()
|
||||
}
|
||||
|
||||
// TODO: support non-maven artifacts
|
||||
|
||||
return ""
|
||||
return m.PURL
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package pkg
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -110,38 +109,3 @@ func TestPomProperties_PkgTypeIndicated(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJavaMetadata_pURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
metadata JavaMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
metadata: JavaMetadata{
|
||||
PomProperties: &PomProperties{
|
||||
Path: "p",
|
||||
Name: "n",
|
||||
GroupID: "g.id",
|
||||
ArtifactID: "a",
|
||||
Version: "v",
|
||||
},
|
||||
},
|
||||
expected: "pkg:maven/g.id/a@v",
|
||||
},
|
||||
{
|
||||
metadata: JavaMetadata{},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
actual := test.metadata.PackageURL(nil)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,6 +1,10 @@
|
||||
package pkg
|
||||
|
||||
import "github.com/anchore/packageurl-go"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
)
|
||||
|
||||
// Language represents a single programming language.
|
||||
type Language string
|
||||
@@ -39,16 +43,20 @@ func LanguageFromPURL(p string) Language {
|
||||
return UnknownLanguage
|
||||
}
|
||||
|
||||
switch purl.Type {
|
||||
case packageurl.TypeMaven, purlGradlePkgType:
|
||||
return LanguageByName(purl.Type)
|
||||
}
|
||||
|
||||
func LanguageByName(name string) Language {
|
||||
switch strings.ToLower(name) {
|
||||
case packageurl.TypeMaven, string(purlGradlePkgType), string(JavaPkg), string(Java):
|
||||
return Java
|
||||
case packageurl.TypeComposer:
|
||||
case packageurl.TypeComposer, string(PhpComposerPkg), string(PHP):
|
||||
return PHP
|
||||
case packageurl.TypeGolang:
|
||||
case packageurl.TypeGolang, string(GoModulePkg), string(Go):
|
||||
return Go
|
||||
case packageurl.TypeNPM:
|
||||
case packageurl.TypeNPM, string(JavaScript):
|
||||
return JavaScript
|
||||
case packageurl.TypePyPi:
|
||||
case packageurl.TypePyPi, string(Python):
|
||||
return Python
|
||||
case packageurl.TypeGem:
|
||||
return Ruby
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
@@ -21,10 +23,19 @@ type NpmPackageJSONMetadata struct {
|
||||
|
||||
// PackageURL returns the PURL for the specific NPM package (see https://github.com/package-url/purl-spec)
|
||||
func (p NpmPackageJSONMetadata) PackageURL(_ *linux.Release) string {
|
||||
var namespace string
|
||||
name := p.Name
|
||||
|
||||
fields := strings.SplitN(p.Name, "/", 2)
|
||||
if len(fields) > 1 {
|
||||
namespace = fields[0]
|
||||
name = fields[1]
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeNPM,
|
||||
"",
|
||||
p.Name,
|
||||
namespace,
|
||||
name,
|
||||
p.Version,
|
||||
nil,
|
||||
"",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNpmPackageJSONMetadata_PackageURL(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata NpmPackageJSONMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no namespace",
|
||||
metadata: NpmPackageJSONMetadata{
|
||||
Name: "arborist",
|
||||
Version: "2.6.2",
|
||||
},
|
||||
expected: "pkg:npm/arborist@2.6.2",
|
||||
},
|
||||
{
|
||||
name: "split by namespace",
|
||||
metadata: NpmPackageJSONMetadata{
|
||||
Name: "@npmcli/arborist",
|
||||
Version: "2.6.2",
|
||||
},
|
||||
expected: "pkg:npm/@npmcli/arborist@2.6.2",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := tt.metadata.PackageURL(nil)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
_, err := packageurl.FromString(actual)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func (p PythonDirectURLOriginInfo) vcsURLQualifier() packageurl.Qualifiers {
|
||||
if p.VCS != "" {
|
||||
// Taken from https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs
|
||||
// packageurl-go still doesn't support all qualifier names
|
||||
return packageurl.Qualifiers{{Key: purlVCSURLQualifier, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}}
|
||||
return packageurl.Qualifiers{{Key: PURLQualifierVCSURL, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -56,15 +56,15 @@ func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
|
||||
}
|
||||
|
||||
qualifiers := map[string]string{
|
||||
purlArchQualifier: m.Arch,
|
||||
PURLQualifierArch: m.Arch,
|
||||
}
|
||||
|
||||
if m.Epoch != nil {
|
||||
qualifiers[purlEpochQualifier] = strconv.Itoa(*m.Epoch)
|
||||
qualifiers[PURLQualifierEpoch] = strconv.Itoa(*m.Epoch)
|
||||
}
|
||||
|
||||
if m.SourceRpm != "" {
|
||||
qualifiers[purlUpstreamQualifier] = m.SourceRpm
|
||||
qualifiers[PURLQualifierUpstream] = m.SourceRpm
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
|
||||
+5
-1
@@ -73,7 +73,11 @@ func TypeFromPURL(p string) Type {
|
||||
return UnknownPkg
|
||||
}
|
||||
|
||||
switch purl.Type {
|
||||
return TypeByName(purl.Type)
|
||||
}
|
||||
|
||||
func TypeByName(name string) Type {
|
||||
switch name {
|
||||
case packageurl.TypeDebian, "deb":
|
||||
return DebPkg
|
||||
case packageurl.TypeRPM:
|
||||
|
||||
+7
-7
@@ -11,13 +11,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
purlArchQualifier = "arch"
|
||||
purlDistroQualifier = "distro"
|
||||
purlEpochQualifier = "epoch"
|
||||
purlVCSURLQualifier = "vcs_url"
|
||||
PURLQualifierArch = "arch"
|
||||
PURLQualifierDistro = "distro"
|
||||
PURLQualifierEpoch = "epoch"
|
||||
PURLQualifierVCSURL = "vcs_url"
|
||||
|
||||
// this qualifier is not in the pURL spec, but is used by grype to perform indirect matching based on source information
|
||||
purlUpstreamQualifier = "upstream"
|
||||
// PURLQualifierUpstream this qualifier is not in the pURL spec, but is used by grype to perform indirect matching based on source information
|
||||
PURLQualifierUpstream = "upstream"
|
||||
|
||||
purlCargoPkgType = "cargo"
|
||||
purlGradlePkgType = "gradle"
|
||||
@@ -83,7 +83,7 @@ func purlQualifiers(vars map[string]string, release *linux.Release) (q packageur
|
||||
|
||||
if release != nil && release.ID != "" && release.VersionID != "" {
|
||||
q = append(q, packageurl.Qualifier{
|
||||
Key: purlDistroQualifier,
|
||||
Key: PURLQualifierDistro,
|
||||
Value: fmt.Sprintf("%s-%s", release.ID, release.VersionID),
|
||||
})
|
||||
}
|
||||
|
||||
+4
-14
@@ -141,13 +141,8 @@ func TestPackageURL(t *testing.T) {
|
||||
Version: "bad-v0.1.0",
|
||||
Type: JavaPkg,
|
||||
Metadata: JavaMetadata{
|
||||
PomProperties: &PomProperties{
|
||||
Path: "p",
|
||||
Name: "n",
|
||||
GroupID: "g.id",
|
||||
ArtifactID: "a",
|
||||
Version: "v",
|
||||
},
|
||||
PomProperties: &PomProperties{},
|
||||
PURL: "pkg:maven/g.id/a@v", // assembled by the java cataloger
|
||||
},
|
||||
},
|
||||
|
||||
@@ -160,13 +155,8 @@ func TestPackageURL(t *testing.T) {
|
||||
Version: "bad-v0.1.0",
|
||||
Type: JenkinsPluginPkg,
|
||||
Metadata: JavaMetadata{
|
||||
PomProperties: &PomProperties{
|
||||
Path: "p",
|
||||
Name: "n",
|
||||
GroupID: "g.id",
|
||||
ArtifactID: "a",
|
||||
Version: "v",
|
||||
},
|
||||
PomProperties: &PomProperties{},
|
||||
PURL: "pkg:maven/g.id/a@v", // assembled by the java cataloger
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
+10
-2
@@ -6,6 +6,7 @@ within this package.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -87,7 +88,14 @@ func parseScheme(userInput string) string {
|
||||
}
|
||||
|
||||
func getImageWithRetryStrategy(userInput, location string, imageSource image.Source, registryOptions *image.RegistryOptions) (*image.Image, func(), error) {
|
||||
img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions)
|
||||
ctx := context.TODO()
|
||||
|
||||
var opts []stereoscope.Option
|
||||
if registryOptions != nil {
|
||||
opts = append(opts, stereoscope.WithRegistryOptions(*registryOptions))
|
||||
}
|
||||
|
||||
img, err := stereoscope.GetImageFromSource(ctx, location, imageSource, opts...)
|
||||
if err == nil {
|
||||
// Success on the first try!
|
||||
return img, stereoscope.Cleanup, nil
|
||||
@@ -117,7 +125,7 @@ func getImageWithRetryStrategy(userInput, location string, imageSource image.Sou
|
||||
// We need to determine the image source again, such that this determination
|
||||
// doesn't take scheme parsing into account.
|
||||
imageSource = image.DetermineImagePullSource(userInput)
|
||||
img, err = stereoscope.GetImageFromSource(userInput, imageSource, registryOptions)
|
||||
img, err = stereoscope.GetImageFromSource(ctx, userInput, imageSource, opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -201,6 +202,10 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegistryAuth(t *testing.T) {
|
||||
host := "localhost:17"
|
||||
image := fmt.Sprintf("%s/something:latest", host)
|
||||
args := []string{"packages", "-vv", fmt.Sprintf("registry:%s", image)}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -209,55 +214,55 @@ func TestRegistryAuth(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "fallback to keychain",
|
||||
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||
args: args,
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput(image),
|
||||
assertInOutput("no registry credentials configured, using the default keychain"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "use creds",
|
||||
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||
args: args,
|
||||
env: map[string]string{
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": host,
|
||||
"SYFT_REGISTRY_AUTH_USERNAME": "username",
|
||||
"SYFT_REGISTRY_AUTH_PASSWORD": "password",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput(`using basic auth for registry "localhost:5000"`),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf(`using basic auth for registry "%s"`, host)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "use token",
|
||||
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||
args: args,
|
||||
env: map[string]string{
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": host,
|
||||
"SYFT_REGISTRY_AUTH_TOKEN": "token",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput(`using token for registry "localhost:5000"`),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf(`using token for registry "%s"`, host)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not enough info fallsback to keychain",
|
||||
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||
args: args,
|
||||
env: map[string]string{
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||
"SYFT_REGISTRY_AUTH_AUTHORITY": host,
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput(image),
|
||||
assertInOutput(`no registry credentials configured, using the default keychain`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allows insecure http flag",
|
||||
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||
args: args,
|
||||
env: map[string]string{
|
||||
"SYFT_REGISTRY_INSECURE_USE_HTTP": "true",
|
||||
},
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRootCmdAliasesToPackagesSubcommand(t *testing.T) {
|
||||
@@ -114,3 +117,47 @@ func TestPersistentFlags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFile(t *testing.T) {
|
||||
request := "docker-archive:" + getFixtureImage(t, "image-pkg-coverage")
|
||||
|
||||
envLogFile := filepath.Join(os.TempDir(), "a-pretty-log-file.log")
|
||||
t.Logf("log file path: %s", envLogFile)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
cleanup func()
|
||||
}{
|
||||
{
|
||||
name: "env-var-log-file-name",
|
||||
args: []string{"-vv", request},
|
||||
env: map[string]string{"SYFT_LOG_FILE": envLogFile},
|
||||
assertions: []traitAssertion{
|
||||
func(tb testing.TB, stdout, stderr string, rc int) {
|
||||
tb.Helper()
|
||||
_, err := os.Stat(envLogFile)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
cleanup: func() { assert.NoError(t, os.Remove(envLogFile)) },
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Cleanup(test.cleanup)
|
||||
|
||||
cmd, stdout, stderr := runSyft(t, test.env, test.args...)
|
||||
for _, traitFn := range test.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ setup_snapshot_server() {
|
||||
worker_pid=$!
|
||||
|
||||
# it takes some time for the server to be ready...
|
||||
sleep 0.5
|
||||
sleep 3
|
||||
|
||||
echo "$worker_pid"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user