From 59eef3764f0853a161ae3d4f74ef6ea26af8e991 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 18 Dec 2023 15:55:35 -0500 Subject: [PATCH 01/28] test: strip fixtures of any execution permissions Signed-off-by: Christopher Phillips --- .../classifiers/positive/erlang-25.3.2.7/erlexec | Bin .../classifiers/positive/php-apache-8.2.1/libphp.so | 0 .../classifiers/positive/php-cli-8.2.1/php | 0 .../classifiers/positive/php-fpm-8.2.1/php-fpm | 0 .../positive/postgresql-15beta4/postgres | Bin .../positive/redis-server-2.8.23/redis-server | Bin .../positive/redis-server-4.0.11/redis-server | Bin .../positive/redis-server-5.0.0/redis-server | Bin .../positive/redis-server-6.0.16/redis-server | Bin .../positive/redis-server-7.0.0/redis-server | Bin .../positive/redis-server-7.0.14/redis-server | Bin .../positive/redis-server-7.2.3-amd64/redis-server | Bin .../positive/redis-server-7.2.3-arm64/redis-server | Bin 13 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-4.0.11/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-6.0.16/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.0/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-amd64/redis-server mode change 100755 => 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-4.0.11/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-4.0.11/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-6.0.16/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-6.0.16/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.0/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.0/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-amd64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-amd64/redis-server old mode 100755 new mode 100644 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server old mode 100755 new mode 100644 From f194de306071cbe99bf1f3e0877851dadd00a5e5 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 18 Dec 2023 16:44:12 -0500 Subject: [PATCH 02/28] chore: add lint check for large files Signed-off-by: Christopher Phillips --- .github/scripts/check_binary_fixture_size.sh | 35 ++++++++++++++++++++ Taskfile.yaml | 6 ++++ 2 files changed, 41 insertions(+) create mode 100755 .github/scripts/check_binary_fixture_size.sh diff --git a/.github/scripts/check_binary_fixture_size.sh b/.github/scripts/check_binary_fixture_size.sh new file mode 100755 index 00000000000..a530d7c0522 --- /dev/null +++ b/.github/scripts/check_binary_fixture_size.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if a directory is provided as an argument +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +directory="$1" + +# Check if the directory exists +if [ ! -d "$directory" ]; then + echo "Directory not found: $directory" + exit 1 +fi + +# Use find to locate all files in the directory and its subdirectories +found_large_files=0 +while IFS= read -r -d '' file; do + # Check if the file size is greater than 100 bytes + if [ $(wc -c < "$file") -gt 100 ]; then + echo "File $file is greater than 100 bytes." + found_large_files=1 + fi +done < <(find "$directory" -type f -print0) + +# Check if any large files were found +if [ "$found_large_files" -eq 1 ]; then + echo "Script failed: Some files are greater than 100 bytes." + exit 1 +else + echo "All files in $directory and its subdirectories are 100 bytes or smaller. Script passed." + exit 0 +fi + diff --git a/Taskfile.yaml b/Taskfile.yaml index 1df8b423861..a7bbfc66422 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -54,6 +54,7 @@ tasks: - task: check-licenses - task: lint - task: check-json-schema-drift + - task: check-binary-fixture-size test: desc: Run all levels of test @@ -169,6 +170,11 @@ tasks: cmds: - .github/scripts/json-schema-drift-check.sh + check-binary-fixture-size: + desc: Ensure that the binary test fixtures are not too large + cmds: + - .github/scripts/check_binary_fixture_size.sh syft/pkg/cataloger/binary/test-fixtures/classifiers/positive + ## Testing tasks ################################# From de87abf02930305c68def1d85986ac1415967d83 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Mon, 18 Dec 2023 17:03:57 -0500 Subject: [PATCH 03/28] add helper script to capture binary snippets Signed-off-by: Alex Goodman --- .../test-fixtures/get-fixture-snippet.sh | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100755 syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh diff --git a/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh new file mode 100755 index 00000000000..b30b8542d58 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +# Default values for length and prefix length +LENGTH=100 +PREFIX_LENGTH=10 + +# Function to show usage +usage() { + echo "Usage: $0 [--length ] [--prefix-length ]" + exit 1 +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + --length) + LENGTH="$2" + shift # past argument + shift # past value + ;; + --prefix-length) + PREFIX_LENGTH="$2" + shift # past argument + shift # past value + ;; + *) + if [ -z "$BINARY_FILE" ]; then + BINARY_FILE="$1" + elif [ -z "$PATTERN" ]; then + PATTERN="$1" + else + echo "Unknown option: $1" + usage + fi + shift # past argument + ;; + esac +done + +# check if binary file and pattern are provided +if [ -z "$BINARY_FILE" ] || [ -z "$PATTERN" ]; then + usage +fi + +# check if xxd is even installed +if ! command -v xxd &> /dev/null; then + echo "xxd not found. Please install xxd." + exit 1 +fi + +PATTERN_RESULTS=$(strings -a -t d "$BINARY_FILE" | grep "$PATTERN") + +# if there are multiple matches, prompt the user to select one +if [ $(echo "$PATTERN_RESULTS" | wc -l) -gt 1 ]; then + echo "Multiple string matches found in the binary:" + echo "" + + # show result lines one at a time (in a numbered list) + # but only show everything after the first field (not the offset) + echo "$PATTERN_RESULTS" | cut -d ' ' -f 2- | nl -w 1 -s ') ' + + + echo "" + read -p "Please select a match: " SELECTION + + # if the selection is not a number, exit + if ! [[ "$SELECTION" =~ ^[0-9]+$ ]]; then + echo "Invalid selection." + exit 1 + fi + + # if the selection is out of bounds, exit + if [ "$SELECTION" -gt $(echo "$PATTERN_RESULTS" | wc -l) ]; then + echo "Invalid selection." + exit 1 + fi + + # select the line from the results + PATTERN_RESULTS=$(echo "$PATTERN_RESULTS" | sed -n "${SELECTION}p") +fi + +# search for the pattern in the binary file and capture the offset +OFFSET=$(echo "${PATTERN_RESULTS}" | cut -d ' ' -f 1) + +if [ -z "$OFFSET" ]; then + echo "Pattern not found." + exit 1 +fi + +# adjust the offset to capture prefix length before the match +OFFSET=$(expr "$OFFSET" - "$PREFIX_LENGTH") + +# use xxd to capture the specified length from the calculated offset +SNIPPET=$(xxd -l "$LENGTH" -s "$OFFSET" "$BINARY_FILE") + +# display the output and prompt the user +echo "" +echo "$SNIPPET" +echo "" +read -p "Does this snippet capture what you need? (Y/n) " RESPONSE +RESPONSE=${RESPONSE:-y} + +if [ "$RESPONSE" != "y" ]; then + echo "Exiting with no action taken." + exit 1 +fi + +# generate a text file with metadata and the binary snippet +SHA256=$(sha256sum "$BINARY_FILE" | cut -d ' ' -f 1) +DATE=$(date) +BASE64_PATTERN=$(echo -n "$PATTERN" | base64) +FILENAME=$(basename "$BINARY_FILE") +INFO=$(file -b "$BINARY_FILE") +OUTPUT_FILE="classifiers/positive/$FILENAME-$SHA256-$OFFSET-$LENGTH.snippet" + +cat > "$OUTPUT_FILE" <> "$OUTPUT_FILE" + +echo "Snippet written to $OUTPUT_FILE" From 2bf99715af021d47acc38c62233d86ff743d385d Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:16:17 -0500 Subject: [PATCH 04/28] chore: update scripts and add new dir output for snippets Signed-off-by: Christopher Phillips --- .github/scripts/check_binary_fixture_size.sh | 3 +++ syft/pkg/cataloger/binary/cataloger_test.go | 18 +++++++++--------- .../test-fixtures/get-fixture-snippet.sh | 5 ++++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/scripts/check_binary_fixture_size.sh b/.github/scripts/check_binary_fixture_size.sh index a530d7c0522..f39904999ea 100755 --- a/.github/scripts/check_binary_fixture_size.sh +++ b/.github/scripts/check_binary_fixture_size.sh @@ -8,6 +8,9 @@ fi directory="$1" +# Remove trailing slash using parameter expansion +directory="${directory%/}" + # Check if the directory exists if [ ! -d "$directory" ]; then echo "Directory not found: $directory" diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index e7aa36cd8e3..b470439c863 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -133,13 +133,13 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-memcached-1.6.18", - fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18", + name: "positive-memcached-1.6.14", + fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.14", expected: pkg.Package{ Name: "memcached", - Version: "1.6.18", + Version: "1.6.14", Type: "binary", - PURL: "pkg:generic/memcached@1.6.18", + PURL: "pkg:generic/memcached@1.6.14", Locations: locations("memcached"), Metadata: metadata("memcached-binary"), }, @@ -546,7 +546,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-openjdk", - fixtureDir: "test-fixtures/classifiers/positive/openjdk", + fixtureDir: "test-fixtures/classifiers/positive/openjdk-1.8.0", expected: pkg.Package{ Name: "java", Version: "1.8.0_352-b08", @@ -558,7 +558,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-openjdk-lts", - fixtureDir: "test-fixtures/classifiers/positive/openjdk-lts", + fixtureDir: "test-fixtures/classifiers/positive/openjdk-lts-11.0.17", expected: pkg.Package{ Name: "java", Version: "11.0.17+8-LTS", @@ -570,7 +570,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-oracle", - fixtureDir: "test-fixtures/classifiers/positive/oracle", + fixtureDir: "test-fixtures/classifiers/positive/oracle-java-19.0.1", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", @@ -582,7 +582,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-oracle-macos", - fixtureDir: "test-fixtures/classifiers/positive/oracle-macos", + fixtureDir: "test-fixtures/classifiers/positive/oracle-macos-19.0.1", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", @@ -594,7 +594,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-ibm", - fixtureDir: "test-fixtures/classifiers/positive/ibm", + fixtureDir: "test-fixtures/classifiers/positive/ibm-java-1.8.0", expected: pkg.Package{ Name: "java", Version: "1.8.0-foreman_2022_09_22_15_30-b00", diff --git a/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh index b30b8542d58..9d0659647b7 100755 --- a/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh +++ b/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh @@ -113,7 +113,10 @@ DATE=$(date) BASE64_PATTERN=$(echo -n "$PATTERN" | base64) FILENAME=$(basename "$BINARY_FILE") INFO=$(file -b "$BINARY_FILE") -OUTPUT_FILE="classifiers/positive/$FILENAME-$SHA256-$OFFSET-$LENGTH.snippet" +OUTPUT_DIRECTORY="classifiers/positive/$FILENAME-$PATTERN-$SHA256-$OFFSET-$LENGTH" +mkdir "$OUTPUT_DIRECTORY" + +OUTPUT_FILE="$OUTPUT_DIRECTORY/$FILENAME" cat > "$OUTPUT_FILE" < Date: Tue, 19 Dec 2023 11:35:17 -0500 Subject: [PATCH 05/28] test: update erlang test to new generated format Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 14 +++----------- .../cataloger/binary/default_classifiers.go | 2 +- .../positive/erlang-25.3.2.7/erlexec | Bin 139920 -> 0 bytes .../erlexec | Bin 0 -> 648 bytes 4 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index b470439c863..b4e6866038a 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -723,7 +723,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Version: "25.3.2.7", Type: "binary", PURL: "pkg:generic/erlang@25.3.2.7", - Locations: locations("erlexec"), + Locations: locations("erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec"), Metadata: metadata("erlang-binary"), }, }, @@ -913,14 +913,6 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) { matches = false break } - if m1.Location.RealPath != "" && m1.Location.RealPath != m2.Location.RealPath { - matches = false - break - } - if m1.Location.AccessPath != "" && m1.Location.AccessPath != m2.Location.AccessPath { - matches = false - break - } } } else { matches = false @@ -941,8 +933,8 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) { cmp.Transformer("Locations", func(l file.LocationSet) []file.Location { return l.ToSlice() }), - cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}), - cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"), + cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}, file.LocationData{}, pkg.LicenseSet{}), + cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "Type", "Locations"), )) } } diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/default_classifiers.go index e3ecd58b1c6..681233e4126 100644 --- a/syft/pkg/cataloger/binary/default_classifiers.go +++ b/syft/pkg/cataloger/binary/default_classifiers.go @@ -288,7 +288,7 @@ var defaultClassifiers = []classifier{ FileGlob: "**/erlexec", EvidenceMatcher: fileContentsVersionMatcher( // [NUL]/usr/local/src/otp-25.3.2.7/erts/ - `(?m)\\x00/usr/local/src/otp-(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+?)/erts/`, + `(?m)/usr/local/src/otp-(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+?)/erts/`, ), Package: "erlang", PURL: mustPURL("pkg:generic/erlang@version"), diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec deleted file mode 100644 index 9ad677967f9ada8a0943d0df9bb4c2598e99205e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139920 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¬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 literal 0 HcmV?d00001 From a782fd2cf79f4fd9bd35dfcaafd47d7737c59e5b Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:39:03 -0500 Subject: [PATCH 06/28] test: update memcached to new generator pattern Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 10 +++++----- .../positive/memcached-1.6.18/memcached | 13 ------------- .../memcached | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 18 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index b4e6866038a..e5abd7742f4 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -133,14 +133,14 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-memcached-1.6.14", - fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.14", + name: "positive-memcached-1.6.18", + fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18", expected: pkg.Package{ Name: "memcached", - Version: "1.6.14", + Version: "1.6.18", Type: "binary", - PURL: "pkg:generic/memcached@1.6.14", - Locations: locations("memcached"), + PURL: "pkg:generic/memcached@1.6.18", + Locations: locations("memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached"), Metadata: metadata("memcached-binary"), }, }, diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached deleted file mode 100644 index 26304334214..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached +++ /dev/null @@ -1,13 +0,0 @@ -secs_since_last_cmd -listen -memcached 1.6.18 -udp-port -memcached 1.6.18 -Failed to allocate memory -%s,%s --- -bget -flush_all -VERSION 1.6.18 -quit -BADCLASS invalid class id diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached new file mode 100644 index 00000000000..896e0369cb6 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached @@ -0,0 +1,18 @@ +### generated by script get-fixture-snippet.sh at Tue Dec 19 11:36:59 EST 2023 ### +# filename: memcached +# sha256: 176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019 +# file info: ASCII text, with CRLF, LF line terminators +# base64(search): MS42LjE4 +# start offset: 19 +# length: 100 +### start of binary snippet ### + +listen +memcached 1.6.18 +udp-port +memcached 1.6.18 +Failed to allocate memory +%s,%s +-- +bget +f \ No newline at end of file From d28932588e42c236e040453410c8c949969f32c6 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:46:25 -0500 Subject: [PATCH 07/28] test: update openjdk to named version Signed-off-by: Christopher Phillips --- .../positive/{openjdk => openjdk-1.8.0}/java | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{openjdk => openjdk-1.8.0}/java (100%) diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-1.8.0/java similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk/java rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-1.8.0/java From b945968058de210d54869cf822d53220471f6486 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:47:22 -0500 Subject: [PATCH 08/28] test: move openjdk lts to versioned folder Signed-off-by: Christopher Phillips --- .../{openjdk-lts => openjdk-lts-11.0.17}/java | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{openjdk-lts => openjdk-lts-11.0.17}/java (100%) diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-lts/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-lts-11.0.17/java similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-lts/java rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-lts-11.0.17/java From 5e555309f434496e287bd05f4bffeb81e95a0672 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:50:09 -0500 Subject: [PATCH 09/28] test: rename unversioned java to versioned folders Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 2 +- .../positive/{ibm => ibm-java-1.8.0}/java | Bin .../positive/{oracle => oracle-java-19.0.1}/java | Bin .../{oracle-macos => oracle-macos-java-19.0.1}/java | Bin 4 files changed, 1 insertion(+), 1 deletion(-) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{ibm => ibm-java-1.8.0}/java (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{oracle => oracle-java-19.0.1}/java (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{oracle-macos => oracle-macos-java-19.0.1}/java (100%) diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index e5abd7742f4..7fde32a5e55 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -582,7 +582,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-oracle-macos", - fixtureDir: "test-fixtures/classifiers/positive/oracle-macos-19.0.1", + fixtureDir: "test-fixtures/classifiers/positive/oracle-macos--java-19.0.1", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ibm/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ibm-java-1.8.0/java similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ibm/java rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ibm-java-1.8.0/java diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-java-19.0.1/java similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle/java rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-java-19.0.1/java diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-macos/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-macos-java-19.0.1/java similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-macos/java rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/oracle-macos-java-19.0.1/java From d1f3d1197bbc9cc5804ac5a9ba8eca5a8ff3c464 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 11:59:17 -0500 Subject: [PATCH 10/28] test: migrate bash fixture to new snippet workflow Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 10 +++++----- .../bash | Bin 0 -> 618 bytes .../classifiers/positive/bash-5.2.15/bash | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 7fde32a5e55..d1540a3e321 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -752,14 +752,14 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-bash-5.2.15", - fixtureDir: "test-fixtures/classifiers/positive/bash-5.2.15", + name: "positive-bash-5.1.16", + fixtureDir: "test-fixtures/classifiers/positive/bash-5.1.16", expected: pkg.Package{ Name: "bash", - Version: "5.2.15", + Version: "5.1.16", Type: "binary", - PURL: "pkg:generic/bash@5.2.15", - Locations: locations("bash"), + PURL: "pkg:generic/bash@5.1.16", + Locations: locations("bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash"), Metadata: metadata("bash-binary"), }, }, diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash new file mode 100644 index 0000000000000000000000000000000000000000..0fb07cbeac7d2bdcf94f5125c3a5796a3aeb3670 GIT binary patch literal 618 zcmYLG!HU~35Y4$?G2lxAwqr@QBL@mg*t8VFUa~EeQm7=4)@ohH$Vy3W`zigvLiYzk zI!@A7FdFejdYXAJiXwQz4!sIkgFeDwyz2vDA+hcd_8ya=bA68?9U4#p9`^|MXn<9~ z*s>^>qJs7I5qQQ0L{aoA0_$4r)OTFMb!jy;1P_hkbpHkda3e%%N`btTlGWU1+=^T* zs*LBjFoNaUV6osPlSV5gSf!QG8V#>(C9uBz1g^8Y8|!-W2Fg6qF2H8Hg5DwE0nI)r z-C_&}Stey3!w>X>+jYQTy8XEQFpHr+Qp3)uwmm}Yx^Gy=#2e7}9%+Yp>vWmV+d84@ z{*YXm(qWhK7*>1N*6;2=(SYppYavuo6nQ3ci<&DdBucX=rX$_3%%=k?Z<^T>9=7>m^L5QfErap_cGeCU#@QKX8LKj? zkn~TX`L8m|2ql;Qz5wky<;m!)s~5`*jX1fiyJ<9AkzM~JDpHoRa>{1lu|;ZV?07PH demR|Aex1%=DBn>4#n0cT^E1T-zvc0KjX$px&dvY; literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash deleted file mode 100644 index 17c8fc2a020..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash +++ /dev/null @@ -1 +0,0 @@ -@(#)Bash version 5.2.15(1) release GNU From 0b4f9ad9bb64667d469e734ef65402edd2589dbc Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 12:16:44 -0500 Subject: [PATCH 11/28] test: update script to size 600 bytes Signed-off-by: Christopher Phillips --- .github/scripts/check_binary_fixture_size.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/scripts/check_binary_fixture_size.sh b/.github/scripts/check_binary_fixture_size.sh index f39904999ea..4518d3bc5eb 100755 --- a/.github/scripts/check_binary_fixture_size.sh +++ b/.github/scripts/check_binary_fixture_size.sh @@ -1,5 +1,8 @@ #!/bin/bash +# current limit for fixture size +size=600 + # Check if a directory is provided as an argument if [ $# -eq 0 ]; then echo "Usage: $0 " @@ -20,19 +23,19 @@ fi # Use find to locate all files in the directory and its subdirectories found_large_files=0 while IFS= read -r -d '' file; do - # Check if the file size is greater than 100 bytes - if [ $(wc -c < "$file") -gt 100 ]; then - echo "File $file is greater than 100 bytes." + # Check if the file size is greater than 600 bytes + if [ $(wc -c < "$file") -gt $size ]; then + echo "File $file is greater than 600 bytes." found_large_files=1 fi done < <(find "$directory" -type f -print0) # Check if any large files were found if [ "$found_large_files" -eq 1 ]; then - echo "Script failed: Some files are greater than 100 bytes." + echo "Script failed: Some files are greater than 600 bytes." exit 1 else - echo "All files in $directory and its subdirectories are 100 bytes or smaller. Script passed." + echo "All files in $directory and its subdirectories are 600 bytes or smaller. Script passed." exit 0 fi From b92ccf8dc7c0af414e786f23fa8fb28a74b1806d Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 12:21:16 -0500 Subject: [PATCH 12/28] test: update go classifier to new snippet workflow Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 8 ++++---- .../test-fixtures/classifiers/positive/go-1.14/go | Bin 21 -> 0 bytes .../go | Bin 0 -> 444 bytes 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index d1540a3e321..212827a810b 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -503,12 +503,12 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-go", - fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + fixtureDir: "test-fixtures/classifiers/positive/go-1.21.3", expected: pkg.Package{ Name: "go", - Version: "1.14", - PURL: "pkg:generic/go@1.14", - Locations: locations("go"), + Version: "1.21.3", + PURL: "pkg:generic/go@1.21.3", + Locations: locations("go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go"), Metadata: metadata("go-binary"), }, }, diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go deleted file mode 100644 index f18b07b8a2b9ed0f79e4b8fadf9691b6bc0ac23d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21 WcmY#N&o|UFRN?|rMqtv=gaH6Iy#&Aj diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go new file mode 100644 index 0000000000000000000000000000000000000000..f0eff60bb31ae974adb5c2e58e7aed59d4d91647 GIT binary patch literal 444 zcmYLFJx{|h5KR{b2ADu%J1GkYrHY-{`H&bY!B{c0JF?SrVyPq{aio@sUjZ9G0R9IP z0|Of?|A7^ow&E=N`JVmG=jZ!AlvraY3@$*fp)We!8q1kL>6IBe4EkEPEt+v(fiN&1 z5H7I*k^!MP$#R;))oczlq!IYOzwd+6ORU8$=CFyfv1DHf%J_}~;38yl!4gzTQ6{nl zPpM3ivI1E`5fh~n8mU+@Sx6~(OtX+sJCMdn#M$;8(6wr|Ri~n;f@|P$AhiKpVKEpX zmk6tr-|*Nfr09{yr#*_UsLpbj&f>M5_pU!9Is=VTJsMXpNs}zfLVIFAQ=00(a}rwC z`nFSm)V1iWbsL}lOz<8&cpZ5*%7%<78AopKq7}6+YQ4N5n|!x^o?p(L;PL4D&Edz> Z$>8wWdwF+vCVO{ZE}tBaejFYi`~nLxa%TVl literal 0 HcmV?d00001 From fd26fc124fd962f5c71210b529a82b193ac0f345 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 12:27:43 -0500 Subject: [PATCH 13/28] test: move haproxy new new snippet Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 2 +- .../classifiers/positive/haproxy-1.5.14/haproxy | 1 - .../haproxy | Bin 0 -> 638 bytes 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 212827a810b..93074ebeef6 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -236,7 +236,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Version: "1.5.14", Type: "binary", PURL: "pkg:generic/haproxy@1.5.14", - Locations: locations("haproxy"), + Locations: locations("haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy"), Metadata: metadata("haproxy-binary"), }, }, diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy deleted file mode 100644 index 7fc2c2a35b4..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy +++ /dev/null @@ -1 +0,0 @@ -HA-Proxy version 1.5.14 2015/07/02Copyright 2000-2015 Willy Tarreau diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy new file mode 100644 index 0000000000000000000000000000000000000000..f2f6760e407ba66ff72ac8a57996fb2cd6b28c02 GIT binary patch literal 638 zcmYL{ZHv?}6ovP*zv4m>cA@PgX_B^sg04Hhz$n6uiik+k+)SgjEoth`{Cm@#SvDU6 zxhJ{FIZu)#fdlsFRX_v!1XJVP7-B7C)*Zv_F`Iffju`T(1r=aFBivyFvq1FZ7^y33n69-H7OT1;m6n84Yq;T;%fR(^xOClY--6_sb^*4#TfieWb5Od&6poe5 zBv0WV`pFGFP`KWG-+jGF!JOiIU8B101fA>uph;uTfPVBCXIOPkOTOw%7S;Kfojc{z zkh2u*;Njh;FRSe$0n261WD2*l>&%C{AJH=X`H~wgNLf%M#@2#JWh-L^CzWhy)6|Nv zXjjxUg?krTFsSFluf Date: Tue, 19 Dec 2023 12:36:50 -0500 Subject: [PATCH 14/28] test: add flatter haproxy example Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 2 +- .../haproxy | Bin 0 -> 642 bytes .../classifiers/positive/haproxy-1.8.22/haproxy | Bin 109 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100/haproxy delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22/haproxy diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 93074ebeef6..0db8c478b32 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -242,7 +242,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-haproxy-1.8.22", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.8.22", + fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100", expected: pkg.Package{ Name: "haproxy", Version: "1.8.22", diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100/haproxy new file mode 100644 index 0000000000000000000000000000000000000000..91b005944668623e9eb44dd968c72a5d7e5792af GIT binary patch literal 642 zcmYLH?~Bwh5WUa$R}2*44%%*#wn@4u=y~UPu!^v!A|jF>(`~f2B~88E|K4o^m z^D=KT?@f{fFR5ko$;paMM32)Af})tm6Ok0LB!|uv(xj8pav7tSYSJv?4?r zTGxtNQyZ;lA!Q*7+6ak8W33xgvC@|3X5ji}IJ4eu-T^N&?E-AJH!wN`+@qO;(jBI- zmptQT3V+d0Zs>u+)%M%=%XJF&5dZ9q>be7TuK$fTjl2Q<=rNwK>YV1~s@yhl_+(| zYDsF*=oFq@Xu+bMcR!cGQs{>OQ;5clXyeFQO~}iu36(eP^%_2G%X0JU;V5uWKETj4 z6NW{QC>$3hi$&3rU1*=Br-WQayCUzY0<`Ouj|*^e`)K8huCw{Y67Sz+pB4|Fb<1VW uSahA%D@sx1rmA;AmHJs8&H&AQK?XtT2!2wpQm7`XQ5|gq+n!VXsK^#pl@UfR>t6* xUrS1xYJ}XXfNoDug5!6{RMYD%g~RxDFYK1t9bE@{7{#xBwY*9>f3u From 48cfc6063136c45844d5a5c6a78ac4902c962d83 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 19 Dec 2023 13:25:57 -0500 Subject: [PATCH 15/28] test: update tests to new pattern Signed-off-by: Christopher Phillips --- syft/pkg/cataloger/binary/cataloger_test.go | 20 +++++++++--------- .../bash | Bin .../erlexec | Bin .../go | Bin .../haproxy | Bin .../haproxy | Bin 0 -> 642 bytes .../httpd | Bin 0 -> 639 bytes .../classifiers/positive/httpd-2.4.54/httpd | 18 ---------------- 8 files changed, 10 insertions(+), 28 deletions(-) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{bash-5.1.16 => }/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{erlang-25.3.2.7 => }/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{go-1.21.3 => }/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/{haproxy-1.5.14 => }/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy (100%) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100/haproxy create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54-3e11fbb6fa371cc9c3e513f49f0ab247b482773cd312248eb86e51251e36fafd-546892-100/httpd delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 0db8c478b32..f9dfc07295c 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -146,7 +146,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-httpd-2.4.54", - fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54", + fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54-3e11fbb6fa371cc9c3e513f49f0ab247b482773cd312248eb86e51251e36fafd-546892-100", expected: pkg.Package{ Name: "httpd", Version: "2.4.54", @@ -230,13 +230,13 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-haproxy-1.5.14", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.5.14", + fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100", expected: pkg.Package{ Name: "haproxy", Version: "1.5.14", Type: "binary", PURL: "pkg:generic/haproxy@1.5.14", - Locations: locations("haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy"), + Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, @@ -254,7 +254,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-haproxy-2.7.3", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-2.7.3", + fixtureDir: "test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100", expected: pkg.Package{ Name: "haproxy", Version: "2.7.3", @@ -503,7 +503,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-go", - fixtureDir: "test-fixtures/classifiers/positive/go-1.21.3", + fixtureDir: "test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100", expected: pkg.Package{ Name: "go", Version: "1.21.3", @@ -582,7 +582,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-java-oracle-macos", - fixtureDir: "test-fixtures/classifiers/positive/oracle-macos--java-19.0.1", + fixtureDir: "test-fixtures/classifiers/positive/oracle-macos-java-19.0.1", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", @@ -717,13 +717,13 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-erlang-25.3.2.7", - fixtureDir: "test-fixtures/classifiers/positive/erlang-25.3.2.7", + fixtureDir: "test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100", expected: pkg.Package{ Name: "erlang", Version: "25.3.2.7", Type: "binary", PURL: "pkg:generic/erlang@25.3.2.7", - Locations: locations("erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec"), + Locations: locations("erlexec"), Metadata: metadata("erlang-binary"), }, }, @@ -753,13 +753,13 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-bash-5.1.16", - fixtureDir: "test-fixtures/classifiers/positive/bash-5.1.16", + fixtureDir: "test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100", expected: pkg.Package{ Name: "bash", Version: "5.1.16", Type: "binary", PURL: "pkg:generic/bash@5.1.16", - Locations: locations("bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash"), + Locations: locations("bash"), Metadata: metadata("bash-binary"), }, }, diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlang-25.3.2.7/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy similarity index 100% rename from syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy rename to syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100/haproxy new file mode 100644 index 0000000000000000000000000000000000000000..177b05511d8a1135d28a1cd4fd29b2b881ee8ad4 GIT binary patch literal 642 zcmYLH&2H2%5Z-ey@CYO2(n?9zj$?1KQV&!uKhhQr8Nfcvnx!O$d+Te^sK(uw!(cJAorl2(VS3bIWF-d_`T2m-RrSRhZf(vs zUvDoD1bgW`bjA$m4}>tv1t(dk1W9(j{!cw6t0(kB{C*EHSzu=x2bJ@)@T`Nv;#{O9@XA5VVqFvsxl7a)_dL;wH) literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54-3e11fbb6fa371cc9c3e513f49f0ab247b482773cd312248eb86e51251e36fafd-546892-100/httpd b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54-3e11fbb6fa371cc9c3e513f49f0ab247b482773cd312248eb86e51251e36fafd-546892-100/httpd new file mode 100644 index 0000000000000000000000000000000000000000..6290cfc50dad49ffd0a16edcb2f71dd34ccce7eb GIT binary patch literal 639 zcmYjPZEM>w5ccQ(iX;Bgg6&u@cAStFR@#k0HU?=2V~nAfQ>@LkjU;!;zn{D;Z2BOM z=k6ZeJ&)2fg#-5JL_iJl1YS8ihKPmS*khO+=DxS%h@tQ;hyYI$!UHNGYanbx>J2a9 zasLFgqzuwDy-LB@4tw!}8#tdfgi%M_w}Mvn1qEP8h>@~3f~|;BYsIi4%<#1-g`|8X zc|%t#rZgjz@&;v7#{yarWU6h1L$eze~2uOL#TMckpS5GbM|={~IrqzX4p}yY>J8 literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd deleted file mode 100644 index 441662ab1a6..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd +++ /dev/null @@ -1,18 +0,0 @@ -pR7`Xr_xeZhR delta 29 kcmeBS{m(MNfz{B=%EVx!YaJtpZ>EWXPgbbG [--length ] [--prefix-length ]" + echo "Usage: $0 [--search-for ] [--length ] [--prefix-length ]" exit 1 } @@ -15,6 +16,11 @@ while [[ $# -gt 0 ]]; do key="$1" case $key in + --search-for) + SEARCH_FOR="$2" + shift # past argument + shift # past value + ;; --length) LENGTH="$2" shift # past argument @@ -28,8 +34,8 @@ while [[ $# -gt 0 ]]; do *) if [ -z "$BINARY_FILE" ]; then BINARY_FILE="$1" - elif [ -z "$PATTERN" ]; then - PATTERN="$1" + elif [ -z "$VERSION" ]; then + VERSION="$1" else echo "Unknown option: $1" usage @@ -40,7 +46,7 @@ while [[ $# -gt 0 ]]; do done # check if binary file and pattern are provided -if [ -z "$BINARY_FILE" ] || [ -z "$PATTERN" ]; then +if [ -z "$BINARY_FILE" ] || [ -z "$VERSION" ]; then usage fi @@ -50,6 +56,9 @@ if ! command -v xxd &> /dev/null; then exit 1 fi + +PATTERN=${SEARCH_FOR:-$VERSION} + PATTERN_RESULTS=$(strings -a -t d "$BINARY_FILE" | grep "$PATTERN") # if there are multiple matches, prompt the user to select one @@ -113,7 +122,7 @@ DATE=$(date) BASE64_PATTERN=$(echo -n "$PATTERN" | base64) FILENAME=$(basename "$BINARY_FILE") INFO=$(file -b "$BINARY_FILE") -OUTPUT_DIRECTORY="classifiers/positive/$FILENAME-$PATTERN-$SHA256-$OFFSET-$LENGTH" +OUTPUT_DIRECTORY="classifiers/positive/$FILENAME-$VERSION" mkdir "$OUTPUT_DIRECTORY" OUTPUT_FILE="$OUTPUT_DIRECTORY/$FILENAME" From bab414288142522ce35fdc77427abe3a1a532911 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 19 Dec 2023 16:14:20 -0500 Subject: [PATCH 17/28] [wip] download bin helpers Signed-off-by: Alex Goodman --- .../test-fixtures/get-binary-fixtures.sh | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100755 syft/pkg/cataloger/binary/test-fixtures/get-binary-fixtures.sh diff --git a/syft/pkg/cataloger/binary/test-fixtures/get-binary-fixtures.sh b/syft/pkg/cataloger/binary/test-fixtures/get-binary-fixtures.sh new file mode 100755 index 00000000000..48dbc38dace --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/get-binary-fixtures.sh @@ -0,0 +1,120 @@ +#!/bin/bash +set -eu -o pipefail + +DESTINATION_DIR="bin" + +curate_destination() { + organization_name=$1 + binary_name=$2 + version=$3 + arch=$4 + + # translate all / into - + arch=$(echo $arch | tr '/' '-') + + # Create directory and define file path + dir_path="${DESTINATION_DIR}/${organization_name}-${version}/${arch}" + mkdir -p "$dir_path" + file_path="${dir_path}/${binary_name}" + + echo $file_path +} + +# function to get a binary from a container +docker_copy_binary() { + local image=$1 + local platform=$2 + local binary_path=$3 + local binary_name=$4 + local version=$5 + local organization_name=${6:-$binary_name} + + + file_path=$(curate_destination $organization_name $binary_name $version $platform) + + # Check if the file already exists + if [ -f "$file_path" ]; then + echo "...$file_path already exists (skipping)" + return + fi + + echo "Pulling $image..." + docker pull "$image" --platform $platform -q + + container_id=$(docker create "$image") + + echo " - copying $binary_path to $file_path..." + docker cp "$container_id:$binary_path" "$file_path" -q + + docker rm "$container_id" +} + + +# let's download stuff! + +docker_copy_binary \ + busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 linux/amd64 /bin/busybox \ + busybox 1.36.1 + +docker_copy_binary \ + bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927 linux/amd64 /usr/local/bin/bash \ + bash 5.1.16 + +docker_copy_binary \ + erlang:25.3.2.6@sha256:0d1e530ec0e8047094f0a1d841754515bad9b0554260a3147fb34df31b3064fe linux/amd64 /usr/local/lib/erlang/bin/erl \ + erlang 25.3.2.6 + +docker_copy_binary \ + golang:1.21.3@sha256:3ce8313c3513515040870c55e0c041a2b94f3576a58cfd3948633604214aa811 linux/amd64 /usr/local/go/bin/go \ + go 1.21.3 + +docker_copy_binary \ + haproxy:1.5.14@sha256:3d57e3921cc84e860f764e863ce729dd0765e3d28d444775127bc42d68f98e10 linux/amd64 /usr/local/sbin/haproxy \ + haproxy 1.5.14 + +docker_copy_binary \ + haproxy:1.8.22@sha256:acd6d3feb77b3f50e672427756b1375fa479b8aeaf30823051e811d10b98da3f linux/amd64 /usr/local/sbin/haproxy \ + haproxy 1.8.22 + +docker_copy_binary \ + haproxy:2.7.3@sha256:17d8aa6bf16882a294bdcccc757dd4002045f34b719e9f94dfd4801614801aea linux/amd64 /usr/local/sbin/haproxy \ + haproxy 2.7.3 + +docker_copy_binary \ + httpd:2.4.54@sha256:c13feaef62bdb03e65e645f47d9780adea5a080c78eb9e4b3c32e861327262b4 linux/amd64 /usr/local/apache2/bin/httpd \ + httpd 2.4.54 + +docker_copy_binary \ + ibmjava:8@sha256:05ef6b0f754aa3a8cebcec36260a70c234a217b21240a998604f33459037bc08 linux/amd64 /opt/ibm/java/jre/bin/java \ + java 1.8.0_391 java-jre-ibm + +docker_copy_binary \ + mariadb:10.6.15@sha256:92d499d9e02e92dc55c8160ef4004aa07f2e835197b18864ed214ca441e0dcfc linux/amd64 /usr/sbin/mariadbd \ + mariadb 10.6.15 + +docker_copy_binary \ + memcached:1.6.18@sha256:9af8e788d5f7f4dc82fd49cf4a7efff1a0b5b4673085bc88f3ccb8a1c772ab3e linux/amd64 /usr/local/bin/memcached \ + memcached 1.6.18 + +docker_copy_binary \ + mysql:5.6.51@sha256:897086d07d1efa876224b147397ea8d3147e61dd84dce963aace1d5e9dc2802d linux/amd64 /usr/sbin/mysqld \ + mysql 5.6.51 + +docker_copy_binary \ + mysql:8.0.34@sha256:8b8835a2c32cd7357a5d2ea4b49ad870ff519c8c1d4add362803feddf4a0a973 linux/amd64 /usr/sbin/mysqld \ + mysql 8.0.34 + +docker_copy_binary \ + nginx:1.25.1@sha256:73e957703f1266530db0aeac1fd6a3f87c1e59943f4c13eb340bb8521c6041d7 linux/amd64 /usr/sbin/nginx \ + nginx 1.25.1 + +docker_copy_binary \ + openresty/openresty:1.21.4.3-2-alpine-fat@sha256:9f9b9d86f2a0f903b1226c3e8a6790293cbb58e521a186ac0031a030ea42c39b linux/amd64 /usr/local/openresty/nginx/sbin/nginx \ + nginx 1.21.4.3 nginx-openresty + +docker_copy_binary \ + node:19.2.0@sha256:9bf5846b28f63acab0ccb0a39a245fbc414e6b7ecd467282f58016537c06e159 linux/amd64 /usr/local/bin/node \ + node 19.2.0 + +echo "Done!" +tree $DESTINATION_DIR From e516eb4967d2ea68346aa8c744e0deda4725af2b Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 21 Dec 2023 18:06:14 -0500 Subject: [PATCH 18/28] add manager for binary cataloger test fixtures Signed-off-by: Alex Goodman --- go.mod | 9 +- go.sum | 11 + syft/pkg/cataloger/binary/cataloger_test.go | 898 +++++++++--------- .../cataloger/binary/default_classifiers.go | 14 +- .../cataloger/binary/test-fixtures/.gitignore | 1 + .../cataloger/binary/test-fixtures/Makefile | 116 +-- .../cataloger/binary/test-fixtures/README.md | 78 ++ ...-fixture-snippet.sh => capture-snippet.sh} | 40 +- .../snippets/bash/5.1.16/linux-amd64/bash | Bin 0 -> 327 bytes .../snippets/busybox/1.36.1/linux-amd64/[ | Bin 0 -> 326 bytes .../busybox/1.36.1/linux-amd64/busybox | 1 + .../snippets/consul/1.15.2/linux-amd64/consul | 12 + .../erlexec/25.3.2.6/linux-amd64/erlexec | Bin 0 -> 330 bytes .../erlexec/26.2.0.0/linux-amd64/erlexec | Bin 0 -> 330 bytes .../snippets/go/1.21.3/linux-amd64/go | Bin 0 -> 326 bytes .../haproxy/1.5.14/linux-amd64/haproxy | Bin 0 -> 330 bytes .../haproxy/1.8.22/linux-amd64/haproxy | Bin 0 -> 331 bytes .../haproxy/2.7.3/linux-amd64/haproxy | Bin 0 -> 331 bytes .../snippets/helm/3.10.3/linux-amd64/helm | Bin 0 -> 329 bytes .../snippets/helm/3.11.1/linux-amd64/helm | Bin 0 -> 329 bytes .../snippets/httpd/2.4.54/linux-amd64/httpd | Bin 0 -> 328 bytes .../java-jre-ibm/1.8.0_391/linux-amd64/java | Bin 0 -> 325 bytes .../mariadb/10.6.15/linux-amd64/mariadb | Bin 0 -> 330 bytes .../memcached/1.6.18/linux-amd64/memcached | Bin 0 -> 332 bytes .../snippets/mysql/5.6.51/linux-amd64/mysql | Bin 0 -> 329 bytes .../snippets/mysql/8.0.34/linux-amd64/mysql | 8 + .../1.21.4.3/linux-amd64/nginx | Bin 0 -> 329 bytes .../snippets/nginx/1.25.1/linux-amd64/nginx | Bin 0 -> 329 bytes .../snippets/node/19.2.0/linux-amd64/node | Bin 0 -> 329 bytes .../postgres/15.1/linux-amd64/postgres | Bin 0 -> 332 bytes .../postgres/9.6.24/linux-amd64/postgres | Bin 0 -> 332 bytes .../binary/test-fixtures/config.yaml | 472 +++++++++ .../test-fixtures/get-binary-fixtures.sh | 120 --- .../binary/test-fixtures/get-image-file.sh | 15 - .../test-fixtures/manager/internal/cli/cli.go | 39 + .../internal/cli/commands/add_snippet.go | 108 +++ .../manager/internal/cli/commands/download.go | 78 ++ .../manager/internal/cli/commands/list.go | 91 ++ .../manager/internal/cli/commands/root.go | 13 + .../internal/cli/commands/write_snippet.go | 256 +++++ .../manager/internal/config/application.go | 139 +++ .../internal/config/binary_from_image.go | 79 ++ .../manager/internal/download_from_image.go | 157 +++ .../manager/internal/list_entries.go | 197 ++++ .../manager/internal/snippet_metadata.go | 39 + .../manager/internal/ui/action.go | 51 + .../test-fixtures/manager/internal/ui/ansi.go | 17 + .../manager/internal/ui/binary_list.go | 107 +++ .../manager/internal/ui/error.go | 7 + .../manager/internal/ui/title.go | 33 + .../test-fixtures/manager/internal/utils.go | 34 + .../binary/test-fixtures/manager/main.go | 26 + .../manager/testutil/snippet_or_binary.go | 100 ++ 53 files changed, 2655 insertions(+), 711 deletions(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/README.md rename syft/pkg/cataloger/binary/test-fixtures/{get-fixture-snippet.sh => capture-snippet.sh} (76%) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/bash/5.1.16/linux-amd64/bash create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/busybox/1.36.1/linux-amd64/[ create mode 120000 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/busybox/1.36.1/linux-amd64/busybox create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/consul/1.15.2/linux-amd64/consul create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/erlexec/25.3.2.6/linux-amd64/erlexec create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/erlexec/26.2.0.0/linux-amd64/erlexec create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/go/1.21.3/linux-amd64/go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/haproxy/1.5.14/linux-amd64/haproxy create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/haproxy/1.8.22/linux-amd64/haproxy create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/haproxy/2.7.3/linux-amd64/haproxy create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/helm/3.10.3/linux-amd64/helm create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/helm/3.11.1/linux-amd64/helm create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/httpd/2.4.54/linux-amd64/httpd create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-ibm/1.8.0_391/linux-amd64/java create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/mariadb/10.6.15/linux-amd64/mariadb create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/memcached/1.6.18/linux-amd64/memcached create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/mysql/5.6.51/linux-amd64/mysql create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/mysql/8.0.34/linux-amd64/mysql create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/nginx-openresty/1.21.4.3/linux-amd64/nginx create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/nginx/1.25.1/linux-amd64/nginx create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/node/19.2.0/linux-amd64/node create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/15.1/linux-amd64/postgres create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/9.6.24/linux-amd64/postgres create mode 100644 syft/pkg/cataloger/binary/test-fixtures/config.yaml delete mode 100755 syft/pkg/cataloger/binary/test-fixtures/get-binary-fixtures.sh delete mode 100755 syft/pkg/cataloger/binary/test-fixtures/get-image-file.sh create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/cli.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/add_snippet.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/download.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/ansi.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/error.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/title.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/main.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go diff --git a/go.mod b/go.mod index 68e28f4455a..d4629f12aa2 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,12 @@ require ( modernc.org/sqlite v1.28.0 ) +require ( + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be + github.com/charmbracelet/bubbles v0.16.1 + github.com/jedib0t/go-pretty/v6 v6.4.9 +) + require ( dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect @@ -91,9 +97,9 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect - github.com/charmbracelet/bubbles v0.16.1 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/cgroups v1.1.0 // indirect @@ -179,6 +185,7 @@ require ( github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.1 // indirect diff --git a/go.sum b/go.sum index a0a5878abfe..109384eef03 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= @@ -462,6 +464,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77 github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jedib0t/go-pretty/v6 v6.4.9 h1:vZ6bjGg2eBSrJn365qlxGcaWu09Id+LHtrfDWlB2Usc= +github.com/jedib0t/go-pretty/v6 v6.4.9/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -501,6 +505,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -531,6 +537,7 @@ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= @@ -627,6 +634,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= @@ -671,6 +679,8 @@ github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9c github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= @@ -736,6 +746,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index be0e2f9a967..f576d5d5cbd 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -2,7 +2,9 @@ package binary import ( "errors" + "flag" "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil" "io" "strings" "testing" @@ -18,15 +20,16 @@ import ( "github.com/anchore/syft/syft/source" ) +var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "force the use of binaries for testing (instead of snippets)") + func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { tests := []struct { - name string - fixtureDir string - expected pkg.Package + name string + logicalFixture string + expected pkg.Package }{ { - name: "positive-postgresql-15beta4", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-15beta4", + logicalFixture: "postgres/15beta4/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "15beta4", @@ -37,8 +40,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-postgresql-15.1", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-15.1", + logicalFixture: "postgres/15.1/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "15.1", @@ -49,8 +51,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-postgresql-9.6.24", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.6.24", + logicalFixture: "postgres/9.6.24/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "9.6.24", @@ -60,21 +61,20 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("postgresql-binary"), }, }, + // TODO: missing original binary + //{ + // logicalFixture: "postgres/9.5alpha1/linux-amd64", + // expected: pkg.Package{ + // Name: "postgresql", + // Version: "9.5alpha1", + // Type: "binary", + // PURL: "pkg:generic/postgresql@9.5alpha1", + // Locations: locations("postgres"), + // Metadata: metadata("postgresql-binary"), + // }, + //}, { - name: "positive-postgresql-9.5alpha1", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.5alpha1", - expected: pkg.Package{ - Name: "postgresql", - Version: "9.5alpha1", - Type: "binary", - PURL: "pkg:generic/postgresql@9.5alpha1", - Locations: locations("postgres"), - Metadata: metadata("postgresql-binary"), - }, - }, - { - name: "positive-mysql-8.0.34", - fixtureDir: "test-fixtures/classifiers/positive/mysql-8.0.34", + logicalFixture: "mysql/8.0.34/linux-amd64", expected: pkg.Package{ Name: "mysql", Version: "8.0.34", @@ -85,8 +85,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-mysql-5.6.51", - fixtureDir: "test-fixtures/classifiers/positive/mysql-5.6.51", + logicalFixture: "mysql/5.6.51/linux-amd64", expected: pkg.Package{ Name: "mysql", Version: "5.6.51", @@ -97,8 +96,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-mariadb-10.6.15", - fixtureDir: "test-fixtures/classifiers/positive/mariadb-10.6.15", + logicalFixture: "mariadb/10.6.15/linux-amd64", expected: pkg.Package{ Name: "mariadb", Version: "10.6.15", @@ -108,45 +106,42 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("mariadb-binary"), }, }, - { - name: "positive-traefik-2.9.6", - fixtureDir: "test-fixtures/classifiers/positive/traefik-2.9.6", - expected: pkg.Package{ - Name: "traefik", - Version: "2.9.6", - Type: "binary", - PURL: "pkg:generic/traefik@2.9.6", - Locations: locations("traefik"), - Metadata: metadata("traefik-binary"), - }, - }, - { - name: "positive-traefik-1.7.34", - fixtureDir: "test-fixtures/classifiers/positive/traefik-1.7.34", - expected: pkg.Package{ - Name: "traefik", - Version: "1.7.34", - Type: "binary", - PURL: "pkg:generic/traefik@1.7.34", - Locations: locations("traefik"), - Metadata: metadata("traefik-binary"), - }, - }, - { - name: "positive-memcached-1.6.18", - fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18", + // TODO: need to add original binary + //{ + // logicalFixture: "traefik/2.9.6/linux-amd64", + // expected: pkg.Package{ + // Name: "traefik", + // Version: "2.9.6", + // Type: "binary", + // PURL: "pkg:generic/traefik@2.9.6", + // Locations: locations("traefik"), + // Metadata: metadata("traefik-binary"), + // }, + //}, + //{ + // logicalFixture: "test-fixtures/classifiers/positive/traefik-1.7.34/linux-amd64", + // expected: pkg.Package{ + // Name: "traefik", + // Version: "1.7.34", + // Type: "binary", + // PURL: "pkg:generic/traefik@1.7.34", + // Locations: locations("traefik"), + // Metadata: metadata("traefik-binary"), + // }, + //}, + { + logicalFixture: "memcached/1.6.18/linux-amd64", expected: pkg.Package{ Name: "memcached", Version: "1.6.18", Type: "binary", PURL: "pkg:generic/memcached@1.6.18", - Locations: locations("memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached"), + Locations: locations("memcached"), Metadata: metadata("memcached-binary"), }, }, { - name: "positive-httpd-2.4.54", - fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54", + logicalFixture: "httpd/2.4.54/linux-amd64", expected: pkg.Package{ Name: "httpd", Version: "2.4.54", @@ -156,81 +151,80 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("httpd-binary"), }, }, - { - name: "positive-php-cli-8.2.1", - fixtureDir: "test-fixtures/classifiers/positive/php-cli-8.2.1", - expected: pkg.Package{ - Name: "php-cli", - Version: "8.2.1", - Type: "binary", - PURL: "pkg:generic/php-cli@8.2.1", - Locations: locations("php"), - Metadata: metadata("php-cli-binary"), - }, - }, - { - name: "positive-php-fpm-8.2.1", - fixtureDir: "test-fixtures/classifiers/positive/php-fpm-8.2.1", - expected: pkg.Package{ - Name: "php-fpm", - Version: "8.2.1", - Type: "binary", - PURL: "pkg:generic/php-fpm@8.2.1", - Locations: locations("php-fpm"), - Metadata: metadata("php-fpm-binary"), - }, - }, - { - name: "positive-php-apache-8.2.1", - fixtureDir: "test-fixtures/classifiers/positive/php-apache-8.2.1", - expected: pkg.Package{ - Name: "libphp", - Version: "8.2.1", - Type: "binary", - PURL: "pkg:generic/php@8.2.1", - Locations: locations("libphp.so"), - Metadata: metadata("php-apache-binary"), - }, - }, - { - name: "positive-perl-5.12.5", - fixtureDir: "test-fixtures/classifiers/positive/perl-5.12.5", - expected: pkg.Package{ - Name: "perl", - Version: "5.12.5", - Type: "binary", - PURL: "pkg:generic/perl@5.12.5", - Locations: locations("perl"), - Metadata: metadata("perl-binary"), - }, - }, - { - name: "positive-perl-5.20.0", - fixtureDir: "test-fixtures/classifiers/positive/perl-5.20.0", - expected: pkg.Package{ - Name: "perl", - Version: "5.20.0", - Type: "binary", - PURL: "pkg:generic/perl@5.20.0", - Locations: locations("perl"), - Metadata: metadata("perl-binary"), - }, - }, - { - name: "positive-perl-5.37.8", - fixtureDir: "test-fixtures/classifiers/positive/perl-5.37.8", - expected: pkg.Package{ - Name: "perl", - Version: "5.37.8", - Type: "binary", - PURL: "pkg:generic/perl@5.37.8", - Locations: locations("perl"), - Metadata: metadata("perl-binary"), - }, - }, - { - name: "positive-haproxy-1.5.14", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100", + + // TODO: missing original binaries + //{ + // logicalFixture: "test-fixtures/classifiers/positive/php-cli-8.2.1/linux-amd64", + // expected: pkg.Package{ + // Name: "php-cli", + // Version: "8.2.1", + // Type: "binary", + // PURL: "pkg:generic/php-cli@8.2.1", + // Locations: locations("php"), + // Metadata: metadata("php-cli-binary"), + // }, + //}, + //{ + // logicalFixture: "test-fixtures/classifiers/positive/php-fpm-8.2.1/linux-amd64", + // expected: pkg.Package{ + // Name: "php-fpm", + // Version: "8.2.1", + // Type: "binary", + // PURL: "pkg:generic/php-fpm@8.2.1", + // Locations: locations("php-fpm"), + // Metadata: metadata("php-fpm-binary"), + // }, + //}, + //{ + // logicalFixture: "test-fixtures/classifiers/positive/php-apache-8.2.1/linux-amd64", + // expected: pkg.Package{ + // Name: "libphp", + // Version: "8.2.1", + // Type: "binary", + // PURL: "pkg:generic/php@8.2.1", + // Locations: locations("libphp.so"), + // Metadata: metadata("php-apache-binary"), + // }, + //}, + + // TODO: original binary is different than test fixture + //{ + // logicalFixture: "perl/5.12.5/linux-amd64", + // expected: pkg.Package{ + // Name: "perl", + // Version: "5.12.5", + // Type: "binary", + // PURL: "pkg:generic/perl@5.12.5", + // Locations: locations("perl"), + // Metadata: metadata("perl-binary"), + // }, + //}, + //{ + // name: "positive-perl-5.20.0", + // logicalFixture: "test-fixtures/classifiers/positive/perl-5.20.0/linux-amd64", + // expected: pkg.Package{ + // Name: "perl", + // Version: "5.20.0", + // Type: "binary", + // PURL: "pkg:generic/perl@5.20.0", + // Locations: locations("perl"), + // Metadata: metadata("perl-binary"), + // }, + //}, + //{ + // name: "positive-perl-5.37.8", + // logicalFixture: "test-fixtures/classifiers/positive/perl-5.37.8/linux-amd64", + // expected: pkg.Package{ + // Name: "perl", + // Version: "5.37.8", + // Type: "binary", + // PURL: "pkg:generic/perl@5.37.8", + // Locations: locations("perl"), + // Metadata: metadata("perl-binary"), + // }, + //}, + { + logicalFixture: "haproxy/1.5.14/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "1.5.14", @@ -241,8 +235,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-haproxy-1.8.22", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100", + logicalFixture: "haproxy/1.8.22/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "1.8.22", @@ -253,8 +246,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-haproxy-2.7.3", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100", + logicalFixture: "haproxy/2.7.3/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "2.7.3", @@ -264,21 +256,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("haproxy-binary"), }, }, + { - name: "positive-redis-2.8.23", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-2.8.23", - expected: pkg.Package{ - Name: "redis", - Version: "2.8.23", - Type: "binary", - PURL: "pkg:generic/redis@2.8.23", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-helm-3.11.1", - fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1", + logicalFixture: "helm/3.11.1/linux-amd64", expected: pkg.Package{ Name: "helm", Version: "3.11.1", @@ -289,8 +269,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-helm-3.10.3", - fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.10.3", + logicalFixture: "helm/3.10.3/linux-amd64", expected: pkg.Package{ Name: "helm", Version: "3.10.3", @@ -300,112 +279,129 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("helm"), }, }, - { - name: "positive-redis-4.0.11", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11", - expected: pkg.Package{ - Name: "redis", - Version: "4.0.11", - Type: "binary", - PURL: "pkg:generic/redis@4.0.11", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-5.0.0", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-5.0.0", - expected: pkg.Package{ - Name: "redis", - Version: "5.0.0", - Type: "binary", - PURL: "pkg:generic/redis@5.0.0", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-6.0.16", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-6.0.16", - expected: pkg.Package{ - Name: "redis", - Version: "6.0.16", - Type: "binary", - PURL: "pkg:generic/redis@6.0.16", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-7.0.0", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.0", - expected: pkg.Package{ - Name: "redis", - Version: "7.0.0", - Type: "binary", - PURL: "pkg:generic/redis@7.0.0", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-7.0.14", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.14", - expected: pkg.Package{ - Name: "redis", - Version: "7.0.14", - Type: "binary", - PURL: "pkg:generic/redis@7.0.14", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-7.2.3-amd64", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-amd64", - expected: pkg.Package{ - Name: "redis", - Version: "7.2.3", - Type: "binary", - PURL: "pkg:generic/redis@7.2.3", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-redis-7.2.3-arm64", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64", - expected: pkg.Package{ - Name: "redis", - Version: "7.2.3", - Type: "binary", - PURL: "pkg:generic/redis@7.2.3", - Locations: locations("redis-server"), - Metadata: metadata("redis-binary"), - }, - }, - { - name: "positive-libpython3.7.so", - fixtureDir: "test-fixtures/classifiers/positive/python-binary-lib-3.7", - expected: pkg.Package{ - Name: "python", - Version: "3.7.4", - PURL: "pkg:generic/python@3.7.4", - Locations: locations("libpython3.7.so"), - Metadata: metadata("python-binary-lib"), - }, - }, - { - name: "positive-python-3.11.2-from-shared-lib", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-3.11", + + // TODO: missing original binaries + //{ + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-2.8.23/linux-amd64", + // expected: pkg.Package{ + // Name: "redis", + // Version: "2.8.23", + // Type: "binary", + // PURL: "pkg:generic/redis@2.8.23", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + + //{ + // name: "positive-redis-4.0.11", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-4.0.11/linux-amd64", + // expected: pkg.Package{ + // Name: "redis", + // Version: "4.0.11", + // Type: "binary", + // PURL: "pkg:generic/redis@4.0.11", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-5.0.0", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-5.0.0/linux-amd64", + // expected: pkg.Package{ + // Name: "redis", + // Version: "5.0.0", + // Type: "binary", + // PURL: "pkg:generic/redis@5.0.0", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-6.0.16", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-6.0.16", + // expected: pkg.Package{ + // Name: "redis", + // Version: "6.0.16", + // Type: "binary", + // PURL: "pkg:generic/redis@6.0.16", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-7.0.0", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.0.0", + // expected: pkg.Package{ + // Name: "redis", + // Version: "7.0.0", + // Type: "binary", + // PURL: "pkg:generic/redis@7.0.0", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-7.0.14", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.0.14", + // expected: pkg.Package{ + // Name: "redis", + // Version: "7.0.14", + // Type: "binary", + // PURL: "pkg:generic/redis@7.0.14", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-7.2.3-amd64", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.2.3-amd64", + // expected: pkg.Package{ + // Name: "redis", + // Version: "7.2.3", + // Type: "binary", + // PURL: "pkg:generic/redis@7.2.3", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + //{ + // name: "positive-redis-7.2.3-arm64", + // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64", + // expected: pkg.Package{ + // Name: "redis", + // Version: "7.2.3", + // Type: "binary", + // PURL: "pkg:generic/redis@7.2.3", + // Locations: locations("redis-server"), + // Metadata: metadata("redis-binary"), + // }, + //}, + + // TODO: missing original binaries + //{ + // name: "positive-libpython3.7.so", + // logicalFixture: "test-fixtures/classifiers/positive/python-binary-lib-3.7", + // expected: pkg.Package{ + // Name: "python", + // Version: "3.7.4", + // PURL: "pkg:generic/python@3.7.4", + // Locations: locations("libpython3.7.so"), + // Metadata: metadata("python-binary-lib"), + // }, + //}, + + { + // note: dynamic (non-snippet) test case + logicalFixture: "python-slim-shared-libs/3.11/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.11.2", PURL: "pkg:generic/python@3.11.2", - Locations: locations("python3", "libpython3.11.so.1.0"), + Locations: locations("python3.11", "libpython3.11.so.1.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ - match("python-binary", "python3"), + match("python-binary", "python3.11"), match("python-binary", "libpython3.11.so.1.0"), match("python-binary-lib", "libpython3.11.so.1.0"), }, @@ -413,8 +409,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-python-3.9-from-shared-redhat-lib", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-redhat-3.9", + // note: dynamic (non-snippet) test case + logicalFixture: "python-rhel-shared-libs/3.9/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.9.13", @@ -430,8 +426,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-python-binary-with-version-3.9", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-with-version-3.9", + // note: dynamic (non-snippet) test case + logicalFixture: "python3.9/3.9.16/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.9.2", @@ -445,8 +441,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-python-binary-3.4-alpine", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-3.4-alpine", + // note: dynamic (non-snippet) test case + logicalFixture: "python-alpine-shared-libs/3.4/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.4.10", @@ -461,188 +457,196 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, }, - { - name: "positive-python-3.5-with-incorrect-match", - fixtureDir: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match", - expected: pkg.Package{ - Name: "python", - Version: "3.5.3", - PURL: "pkg:generic/python@3.5.3", - Locations: locations("python3.5"), - Metadata: metadata("python-binary"), - }, - }, - { - name: "positive-python3.6", - fixtureDir: "test-fixtures/classifiers/positive/python-binary-3.6", - expected: pkg.Package{ - Name: "python", - Version: "3.6.3", - PURL: "pkg:generic/python@3.6.3", - Locations: locations("python3.6"), - Metadata: metadata("python-binary"), - }, - }, - { - name: "positive-python-duplicates", - fixtureDir: "test-fixtures/classifiers/positive/python-duplicates", - expected: pkg.Package{ - Name: "python", - Version: "3.8.16", - Type: "binary", - PURL: "pkg:generic/python@3.8.16", - Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), - Metadata: pkg.BinarySignature{ - Matches: []pkg.ClassifierMatch{ - match("python-binary", "dir/python3.8"), - match("python-binary", "python3.8"), - match("python-binary-lib", "libpython3.8.so"), - }, - }, - }, - }, - { - name: "positive-go", - fixtureDir: "test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100", + // TODO: missing original binary + //{ + // name: "positive-python-3.5-with-incorrect-match", + // logicalFixture: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match", + // expected: pkg.Package{ + // Name: "python", + // Version: "3.5.3", + // PURL: "pkg:generic/python@3.5.3", + // Locations: locations("python3.5"), + // Metadata: metadata("python-binary"), + // }, + //}, + + // TODO: can't seem to get this lined up with the new binary pulled down... the original snippet also has content I can't account for + //{ + // logicalFixture: "python/3.6/linux-amd64", + // expected: pkg.Package{ + // Name: "python", + // Version: "3.6.3", + // PURL: "pkg:generic/python@3.6.3", + // Locations: locations("python3.6"), + // Metadata: metadata("python-binary"), + // }, + //}, + + // TODO: missing original binary + //{ + // name: "positive-python-duplicates", + // logicalFixture: "test-fixtures/classifiers/positive/python-duplicates", + // expected: pkg.Package{ + // Name: "python", + // Version: "3.8.16", + // Type: "binary", + // PURL: "pkg:generic/python@3.8.16", + // Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), + // Metadata: pkg.BinarySignature{ + // Matches: []pkg.ClassifierMatch{ + // match("python-binary", "dir/python3.8"), + // match("python-binary", "python3.8"), + // match("python-binary-lib", "libpython3.8.so"), + // }, + // }, + // }, + //}, + + { + logicalFixture: "go/1.21.3/linux-amd64", expected: pkg.Package{ Name: "go", Version: "1.21.3", PURL: "pkg:generic/go@1.21.3", - Locations: locations("go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go"), + Locations: locations("go"), Metadata: metadata("go-binary"), }, }, { - name: "positive-node", - fixtureDir: "test-fixtures/classifiers/positive/node-19.2.1", + logicalFixture: "node/19.2.0/linux-amd64", expected: pkg.Package{ Name: "node", - Version: "19.2.1", - PURL: "pkg:generic/node@19.2.1", + Version: "19.2.0", + PURL: "pkg:generic/node@19.2.0", Locations: locations("node"), Metadata: metadata("nodejs-binary"), }, }, + // TODO: captured outside of the original binary + //{ + // name: "positive-go-hint", + // logicalFixture: "test-fixtures/classifiers/positive/go-hint-1.15", + // expected: pkg.Package{ + // Name: "go", + // Version: "1.15", + // PURL: "pkg:generic/go@1.15", + // Locations: locations("VERSION"), + // Metadata: metadata("go-binary-hint"), + // }, + //}, { - name: "positive-go-hint", - fixtureDir: "test-fixtures/classifiers/positive/go-hint-1.15", - expected: pkg.Package{ - Name: "go", - Version: "1.15", - PURL: "pkg:generic/go@1.15", - Locations: locations("VERSION"), - Metadata: metadata("go-binary-hint"), - }, - }, - { - name: "positive-busybox", - fixtureDir: "test-fixtures/classifiers/positive/busybox-3.33.3", + // note: this is testing BUSYBOX which is typically through a link to "[" (in this case a symlink but in + // practice this is often a hard link). + logicalFixture: `busybox/1.36.1/linux-amd64`, expected: pkg.Package{ Name: "busybox", - Version: "3.33.3", + Version: "1.36.1", Locations: locations("["), // note: busybox is a link to [ Metadata: metadata("busybox-binary", "[", "busybox"), }, }, - { - name: "positive-java-openjdk", - fixtureDir: "test-fixtures/classifiers/positive/openjdk-1.8.0", - expected: pkg.Package{ - Name: "java", - Version: "1.8.0_352-b08", - Type: "binary", - PURL: "pkg:generic/java@1.8.0_352-b08", - Locations: locations("java"), - Metadata: metadata("java-binary-openjdk", "java"), - }, - }, - { - name: "positive-java-openjdk-lts", - fixtureDir: "test-fixtures/classifiers/positive/openjdk-lts-11.0.17", - expected: pkg.Package{ - Name: "java", - Version: "11.0.17+8-LTS", - Type: "binary", - PURL: "pkg:generic/java@11.0.17+8-LTS", - Locations: locations("java"), - Metadata: metadata("java-binary-openjdk", "java"), - }, - }, - { - name: "positive-java-oracle", - fixtureDir: "test-fixtures/classifiers/positive/oracle-java-19.0.1", + // TODO: need to get the original binaries + //{ + // logicalFixture: "openjdk/1.8.0", + // expected: pkg.Package{ + // Name: "java", + // Version: "1.8.0_352-b08", + // Type: "binary", + // PURL: "pkg:generic/java@1.8.0_352-b08", + // Locations: locations("java"), + // Metadata: metadata("java-binary-openjdk", "java"), + // }, + //}, + //{ + // name: "positive-java-openjdk-lts", + // logicalFixture: "test-fixtures/classifiers/positive/openjdk-lts-11.0.17", + // expected: pkg.Package{ + // Name: "java", + // Version: "11.0.17+8-LTS", + // Type: "binary", + // PURL: "pkg:generic/java@11.0.17+8-LTS", + // Locations: locations("java"), + // Metadata: metadata("java-binary-openjdk", "java"), + // }, + //}, + //{ + // name: "positive-java-oracle", + // logicalFixture: "test-fixtures/classifiers/positive/oracle-java-19.0.1", + // expected: pkg.Package{ + // Name: "java", + // Version: "19.0.1+10-21", + // Type: "binary", + // PURL: "pkg:generic/java@19.0.1+10-21", + // Locations: locations("java"), + // Metadata: metadata("java-binary-oracle", "java"), + // }, + //}, + //{ + // name: "positive-java-oracle-macos", + // logicalFixture: "test-fixtures/classifiers/positive/oracle-macos-java-19.0.1", + // expected: pkg.Package{ + // Name: "java", + // Version: "19.0.1+10-21", + // Type: "binary", + // PURL: "pkg:generic/java@19.0.1+10-21", + // Locations: locations("java"), + // Metadata: metadata("java-binary-oracle", "java"), + // }, + //}, + { + name: "positive-java-ibm", + logicalFixture: "java-jre-ibm/1.8.0_391/linux-amd64", expected: pkg.Package{ Name: "java", - Version: "19.0.1+10-21", + Version: "1.8.0-foreman_2023_10_12_13_27-b00", Type: "binary", - PURL: "pkg:generic/java@19.0.1+10-21", - Locations: locations("java"), - Metadata: metadata("java-binary-oracle", "java"), - }, - }, - { - name: "positive-java-oracle-macos", - fixtureDir: "test-fixtures/classifiers/positive/oracle-macos-java-19.0.1", - expected: pkg.Package{ - Name: "java", - Version: "19.0.1+10-21", - Type: "binary", - PURL: "pkg:generic/java@19.0.1+10-21", - Locations: locations("java"), - Metadata: metadata("java-binary-oracle", "java"), - }, - }, - { - name: "positive-java-ibm", - fixtureDir: "test-fixtures/classifiers/positive/ibm-java-1.8.0", - expected: pkg.Package{ - Name: "java", - Version: "1.8.0-foreman_2022_09_22_15_30-b00", - Type: "binary", - PURL: "pkg:generic/java@1.8.0-foreman_2022_09_22_15_30-b00", + PURL: "pkg:generic/java@1.8.0-foreman_2023_10_12_13_27-b00", Locations: locations("java"), Metadata: metadata("java-binary-ibm", "java"), }, }, - { - name: "positive-rust-1.50.0-macos", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.50.0", - expected: pkg.Package{ - Name: "rust", - Version: "1.50.0", - Type: "binary", - PURL: "pkg:generic/rust@1.50.0", - Locations: locations("lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib"), - Metadata: metadata("rust-standard-library-macos"), - }, - }, - { - name: "positive-rust-1.67.1-macos", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin", - expected: pkg.Package{ - Name: "rust", - Version: "1.67.1", - Type: "binary", - PURL: "pkg:generic/rust@1.67.1", - Locations: locations("lib/libstd-16f2b65e77054c42.dylib"), - Metadata: metadata("rust-standard-library-macos"), - }, - }, - { - name: "positive-rust-1.67.1-linux", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl", - expected: pkg.Package{ - Name: "rust", - Version: "1.67.1", - Type: "binary", - PURL: "pkg:generic/rust@1.67.1", - Locations: locations("lib/libstd-86aefecbddda356d.so"), - Metadata: metadata("rust-standard-library-linux"), - }, - }, - { - name: "positive-ruby-3.2.1", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-3.2.1", + // TODO: need to get the original binaries + //{ + // name: "positive-rust-1.50.0-macos", + // logicalFixture: "test-fixtures/classifiers/positive/rust-1.50.0", + // expected: pkg.Package{ + // Name: "rust", + // Version: "1.50.0", + // Type: "binary", + // PURL: "pkg:generic/rust@1.50.0", + // Locations: locations("lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib"), + // Metadata: metadata("rust-standard-library-macos"), + // }, + //}, + //{ + // name: "positive-rust-1.67.1-macos", + // logicalFixture: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin", + // expected: pkg.Package{ + // Name: "rust", + // Version: "1.67.1", + // Type: "binary", + // PURL: "pkg:generic/rust@1.67.1", + // Locations: locations("lib/libstd-16f2b65e77054c42.dylib"), + // Metadata: metadata("rust-standard-library-macos"), + // }, + //}, + //{ + // name: "positive-rust-1.67.1-linux", + // logicalFixture: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl", + // expected: pkg.Package{ + // Name: "rust", + // Version: "1.67.1", + // Type: "binary", + // PURL: "pkg:generic/rust@1.67.1", + // Locations: locations("lib/libstd-86aefecbddda356d.so"), + // Metadata: metadata("rust-standard-library-linux"), + // }, + //}, + { + // note: dynamic (non-snippet) test case + + name: "positive-ruby-3.2.1", + logicalFixture: "ruby-bullseye-shared-libs/3.2.1/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "3.2.1", @@ -658,8 +662,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-ruby-2.7.7", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.7.7", + // note: dynamic (non-snippet) test case + logicalFixture: "ruby-bullseye-shared-libs/2.7.7/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "2.7.7p221", @@ -675,8 +679,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-ruby-2.6.10", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.6.10", + // note: dynamic (non-snippet) test case + logicalFixture: "ruby-shared-libs/2.6.10/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "2.6.10p210", @@ -691,21 +695,20 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, }, + //{ + // name: "positive-ruby-1.9.3p551", + // logicalFixture: "test-fixtures/classifiers/positive/ruby-1.9.3p551/linux-amd64", + // expected: pkg.Package{ + // Name: "ruby", + // Version: "1.9.3p551", + // Type: "binary", + // PURL: "pkg:generic/ruby@1.9.3p551", + // Locations: locations("ruby"), + // Metadata: metadata("ruby-binary"), + // }, + //}, { - name: "positive-ruby-1.9.3p551", - fixtureDir: "test-fixtures/classifiers/positive/ruby-1.9.3p551", - expected: pkg.Package{ - Name: "ruby", - Version: "1.9.3p551", - Type: "binary", - PURL: "pkg:generic/ruby@1.9.3p551", - Locations: locations("ruby"), - Metadata: metadata("ruby-binary"), - }, - }, - { - name: "positive-consul-1.15.2", - fixtureDir: "test-fixtures/classifiers/dynamic/consul-1.15.2", + logicalFixture: "consul/1.15.2/linux-amd64", expected: pkg.Package{ Name: "consul", Version: "1.15.2", @@ -716,20 +719,29 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-erlang-25.3.2.7", - fixtureDir: "test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100", + logicalFixture: "erlexec/25.3.2.6/linux-amd64", expected: pkg.Package{ Name: "erlang", - Version: "25.3.2.7", + Version: "25.3.2.6", Type: "binary", - PURL: "pkg:generic/erlang@25.3.2.7", + PURL: "pkg:generic/erlang@25.3.2.6", Locations: locations("erlexec"), Metadata: metadata("erlang-binary"), }, }, { - name: "positive-nginx-1.25.1", - fixtureDir: "test-fixtures/classifiers/positive/nginx-1.25.1", + logicalFixture: "erlexec/26.2.0.0/linux-amd64", + expected: pkg.Package{ + Name: "erlang", + Version: "26.2", + Type: "binary", + PURL: "pkg:generic/erlang@26.2", + Locations: locations("erlexec"), + Metadata: metadata("erlang-binary"), + }, + }, + { + logicalFixture: "nginx/1.25.1/linux-amd64", expected: pkg.Package{ Name: "nginx", Version: "1.25.1", @@ -740,8 +752,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-nginx-openresty-1.21.4.2", - fixtureDir: "test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2", + logicalFixture: "nginx-openresty/1.21.4.3/linux-amd64", expected: pkg.Package{ Name: "nginx", Version: "1.21.4", @@ -752,8 +763,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-bash-5.1.16", - fixtureDir: "test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100", + logicalFixture: "bash/5.1.16/linux-amd64", expected: pkg.Package{ Name: "bash", Version: "5.1.16", @@ -766,10 +776,12 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { + t.Run(test.logicalFixture, func(t *testing.T) { c := NewCataloger() - src, err := source.NewFromDirectoryPath(test.fixtureDir) + path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries) + + src, err := source.NewFromDirectoryPath(path) require.NoError(t, err) resolver, err := src.FileResolver(source.SquashedScope) @@ -778,7 +790,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { packages, _, err := c.Catalog(resolver) require.NoError(t, err) - require.Len(t, packages, 1) + require.Len(t, packages, 1, "mismatched package count") assertPackagesAreEqual(t, test.expected, packages[0]) }) diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/default_classifiers.go index 681233e4126..34f35e90bef 100644 --- a/syft/pkg/cataloger/binary/default_classifiers.go +++ b/syft/pkg/cataloger/binary/default_classifiers.go @@ -233,8 +233,6 @@ var defaultClassifiers = []classifier{ Class: "mysql-binary", FileGlob: "**/mysql", EvidenceMatcher: fileContentsVersionMatcher( - // ../../mysql-8.0.34 - // /mysql-5.6.51/bld/client `(?m).*/mysql-(?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`), Package: "mysql", PURL: mustPURL("pkg:generic/mysql@version"), @@ -286,9 +284,15 @@ var defaultClassifiers = []classifier{ { Class: "erlang-binary", FileGlob: "**/erlexec", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]/usr/local/src/otp-25.3.2.7/erts/ - `(?m)/usr/local/src/otp-(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+?)/erts/`, + EvidenceMatcher: evidenceMatchers( + fileContentsVersionMatcher( + // [NUL]/usr/src/otp_src_25.3.2.6/erts/ + `(?m)/src/otp_src_(?P[0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)?)/erts/`, + ), + fileContentsVersionMatcher( + // [NUL]/usr/local/src/otp-25.3.2.7/erts/ + `(?m)/usr/local/src/otp-(?P[0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)?)/erts/`, + ), ), Package: "erlang", PURL: mustPURL("pkg:generic/erlang@version"), diff --git a/syft/pkg/cataloger/binary/test-fixtures/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/.gitignore index fd45a0e70fc..9a442ff1e01 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/.gitignore +++ b/syft/pkg/cataloger/binary/test-fixtures/.gitignore @@ -1 +1,2 @@ classifiers/dynamic +classifiers/bin \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/Makefile b/syft/pkg/cataloger/binary/test-fixtures/Makefile index 4ed523068e4..2eaf95f4428 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/Makefile +++ b/syft/pkg/cataloger/binary/test-fixtures/Makefile @@ -1,110 +1,24 @@ -.PHONY: all -all: \ - classifiers/dynamic/python-binary-shared-lib-3.11 \ - classifiers/dynamic/python-binary-shared-lib-redhat-3.9 \ - classifiers/dynamic/python-binary-with-version-3.9 \ - classifiers/dynamic/python-binary-3.4-alpine \ - classifiers/dynamic/ruby-library-3.2.1 \ - classifiers/dynamic/ruby-library-2.7.7 \ - classifiers/dynamic/ruby-library-2.6.10 \ - classifiers/dynamic/helm-3.11.1 \ - classifiers/dynamic/helm-3.10.3 \ - classifiers/dynamic/consul-1.15.2 +.PHONY: default list download download-all fingerprint +.DEFAULT_GOAL := default +default: download -classifiers/dynamic/python-binary-shared-lib-3.11: - $(eval $@_image := "python:3.11-slim@sha256:0b106e1d2bf485c2a41474bc9cd5103e9eea4e179f40f10741b53b127059221e") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/python3.11 \ - $@/python3 - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libpython3.11.so.1.0 \ - $@/libpython3.11.so.1.0 +list: ## list all managed binaries and snippets + go run ./manager list -classifiers/dynamic/python-binary-shared-lib-redhat-3.9: - $(eval $@_image := "registry.access.redhat.com/ubi8/python-39@sha256:f3cf958b96ce016b63e3e163e488f52e42891304dafef5a0811563f22e3cbad0") - ./get-image-file.sh $($@_image) \ - /usr/bin/python3.9 \ - $@/python3.9 - ./get-image-file.sh $($@_image) \ - /usr/lib64/libpython3.9.so.1.0 \ - $@/libpython3.9.so.1.0 +download: ## download only binaries that are not covered by a snippet + go run ./manager download $(name) --skip-if-covered-by-snippet -classifiers/dynamic/python-binary-with-version-3.9: - $(eval $@_image := "python:3.9.16-bullseye@sha256:93fb93c461a2e47a2176706fad1f39eaacd5dd40e19c0b018699a28c03eb2e2a") - ./get-image-file.sh $($@_image) \ - /usr/bin/python3.9 \ - $@/python3.9 +download-all: ## download all managed binaries + go run ./manager download -classifiers/dynamic/python-binary-3.4-alpine: - $(eval $@_image := "python:3.4-alpine@sha256:c210b660e2ea553a7afa23b41a6ed112f85dbce25cbcb567c75dfe05342a4c4b") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/python3.4 \ - $@/python3.4 - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libpython3.4m.so.1.0 \ - $@/libpython3.4m.so.1.0 +fingerprint: ## prints the sha256sum of the any input to the download command (to determine if there is a cache miss) + @cat ./config.yaml | sha256sum | awk '{print $$1}' -classifiers/dynamic/ruby-library-3.2.1: - $(eval $@_image := "ruby:3.2.1-bullseye@sha256:b4a140656b0c5d26c0a80559b228b4d343f3fdbf56682fcbe88f6db1fa9afa6b") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/ruby \ - $@/ruby - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.3.2.1 \ - $@/libruby.so.3.2.1 - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.3.2 \ - $@/libruby.so.3.2 -classifiers/dynamic/ruby-library-2.7.7: - $(eval $@_image := "ruby:2.7.7-bullseye@sha256:055191740a063f33fef1f09423e5ed8f91143aae62a3772a90910118464c5120") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/ruby \ - $@/ruby - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.2.7.7 \ - $@/libruby.so.2.7.7 - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.2.7 \ - $@/libruby.so.2.7 +## Halp! ################################# -classifiers/dynamic/ruby-library-2.6.10: - $(eval $@_image := "ruby:2.6.10@sha256:771a810704167e55da8a19970c5dfa6eb795dfee32547adffa29ea72703f7243") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/ruby \ - $@/ruby - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.2.6.10 \ - $@/libruby.so.2.6.10 - ./get-image-file.sh $($@_image) \ - /usr/local/lib/libruby.so.2.6 \ - $@/libruby.so.2.6 - -classifiers/dynamic/helm-3.11.1: - $(eval $@_image := "alpine/helm:3.11.1@sha256:8628e3695fb743a8b9de89626f1b7a221280c2152c0e288c2504e59b68233e8b") - ./get-image-file.sh $($@_image) \ - /usr/bin/helm \ - $@/helm - -classifiers/dynamic/helm-3.10.3: - $(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/helm \ - $@/helm - -classifiers/dynamic/consul-1.15.2: - $(eval $@_image := "hashicorp/consul:1.15.2@sha256:c2169f3bb18dd947ae8eb5f6766896695c71fb439f050a3343e0007d895615b8") - ./get-image-file.sh $($@_image) \ - /bin/consul \ - $@/consul - -.PHONY: clean -clean: - rm -rf classifiers/dynamic - -.PHONY: cache.fingerprint -cache.fingerprint: # for CI - $(title,Install test fixture fingerprint) - @find ./classifiers/dynamic/* -type f -exec md5sum {} + | awk '{print $1}' | sort | tee /dev/stderr | md5sum | tee cache.fingerprint >> cache.fingerprint +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/README.md b/syft/pkg/cataloger/binary/test-fixtures/README.md new file mode 100644 index 00000000000..8b7d42a03a3 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/README.md @@ -0,0 +1,78 @@ +# Binary cataloger test fixtures + +To test the binary cataloger we run it against a set of files ("test fixtures"). There are two kinds of test fixtures: + +- **Full binaries**: files downloaded and cached at test runtime +- **Snippets**: ~100 byte files checked into the repo + +The upside with snippets is that they live with the test, don't necessarily require network access or hosting concerns, and are easy to add. The downside is that they are not the entire real binary so modifications may require recreating the snippet entirely. + +The upside with full binaries is that they are the "Real McCoy" and allows the business logic to change without needing to update the fixture. The downside is that they require network access and take up a lot of space. For instance, downloading all binaries for testing today requires downloading ~15GB of container images and ends up being ~500MB of disk space. + +You can find the test fixtures at the following locations: +``` +syft/pkg/cataloger/binary/test-fixtures/ +└── classifiers/ + ├── bin/ # full binaries + ├── ... + └── snippets/ # snippets +``` + +And use tooling to list and manage the fixtures: + +- `make list` - list all fixtures +- `make download` - download binaries that are not covered by a snippet +- `make download-all` - download all binaries +- `go run ./manager add-snippet` - add a new snippet based off of a configured binary +- `capture-snippet.sh` - add a new snippet based off of a binary on your local machine (not recommended, but allowed) + +There is a `config.yaml` that tracks all binaries that the tests can use. This makes it possible to download it at any time from a hosted source. Today the only method allowed is to download a container image and extract files out. + +## Testing + +The test cases have been setup to allow testing against full binaries or a mix of both (default). +To force running only against full binaries run with: + +```bash +go test -must-use-full-binaries ./syft/pkg/cataloger/binary/test-fixtures/... +``` + +## Adding a new test fixture + +### Adding a full binary + +1. Add a new entry to `config.yaml` with the following fields + - if you are adding a single binary, the `name` field does not need to be specified + - the `name` field is useful for distinguishing a quality about the binary (e.g. `java` vs `java-jre-ibm`) + +2. Run `make download` and ensure your new binary is downloaded + + +### Adding a snippet + +Even if you are adding a snippet, it is best practice to: + +- create that snippet from a full binary (not craft a snippet by hand) +- track where the binary is from and how to download it in `config.yaml` + +1. Follow the steps above to [add a full binary](#adding-a-full-binary) + +2. Run `go run ./manager add-snippet` and follow the prompts to create a new snippet + - you should see your binary in the list of binaries to choose from. If not, check step 2 + - if the search results in no matching snippets, you can specify your own search with `--search-for ` + - you should see a new snippet file created in `snippets/` + +3. Write a test that references your new snippet by `//` + - `` is the name of the binary (e.g. `curl`) or the name in `config.yaml` if specified + - note that your test does not know about if it's running against a snippet or a full binary + +### Adding a custom snippet + +If you need to add a snippet that is not based off of a full binary, you can use the `capture-snippet.sh` script. + +```bash +./capture-snippet.sh [--search-for ] [--length ] [--prefix-length ] [--group ] +``` + + +This is **not** recommended because it is not reproducible and does not allow for the test to be run against a full binary. \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh similarity index 76% rename from syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh rename to syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh index 9d99eaa64da..c154b804a5f 100755 --- a/syft/pkg/cataloger/binary/test-fixtures/get-fixture-snippet.sh +++ b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh @@ -4,10 +4,11 @@ LENGTH=100 PREFIX_LENGTH=10 SEARCH_FOR='' +GROUP_NAME='' # Function to show usage usage() { - echo "Usage: $0 [--search-for ] [--length ] [--prefix-length ]" + echo "Usage: $0 [--search-for ] [--length ] [--prefix-length ] [--group ]" exit 1 } @@ -26,6 +27,11 @@ while [[ $# -gt 0 ]]; do shift # past argument shift # past value ;; + --group) + GROUP_NAME="$2" + shift # past argument + shift # past value + ;; --prefix-length) PREFIX_LENGTH="$2" shift # past argument @@ -50,6 +56,11 @@ if [ -z "$BINARY_FILE" ] || [ -z "$VERSION" ]; then usage fi +# if group name is empty use the binary filename +if [ -z "$GROUP_NAME" ]; then + GROUP_NAME=$(basename "$BINARY_FILE") +fi + # check if xxd is even installed if ! command -v xxd &> /dev/null; then echo "xxd not found. Please install xxd." @@ -59,7 +70,7 @@ fi PATTERN=${SEARCH_FOR:-$VERSION} -PATTERN_RESULTS=$(strings -a -t d "$BINARY_FILE" | grep "$PATTERN") +PATTERN_RESULTS=$(strings -t d "$BINARY_FILE" | grep "$PATTERN") # if there are multiple matches, prompt the user to select one if [ $(echo "$PATTERN_RESULTS" | wc -l) -gt 1 ]; then @@ -116,27 +127,4 @@ if [ "$RESPONSE" != "y" ]; then exit 1 fi -# generate a text file with metadata and the binary snippet -SHA256=$(sha256sum "$BINARY_FILE" | cut -d ' ' -f 1) -DATE=$(date) -BASE64_PATTERN=$(echo -n "$PATTERN" | base64) -FILENAME=$(basename "$BINARY_FILE") -INFO=$(file -b "$BINARY_FILE") -OUTPUT_DIRECTORY="classifiers/positive/$FILENAME-$VERSION" -mkdir "$OUTPUT_DIRECTORY" - -OUTPUT_FILE="$OUTPUT_DIRECTORY/$FILENAME" - -cat > "$OUTPUT_FILE" <> "$OUTPUT_FILE" - -echo "Snippet written to $OUTPUT_FILE" +go run ./manager write-snippet "$BINARY_FILE" --offset "$OFFSET" --length "$LENGTH" --name "$GROUP_NAME" --version "$VERSION" diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/bash/5.1.16/linux-amd64/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/bash/5.1.16/linux-amd64/bash new file mode 100644 index 0000000000000000000000000000000000000000..ff22b21456edcceed00efd2a0a099001b2dadefb GIT binary patch literal 327 zcmXw!!EVDK5JY|ED^{{9C6@@WG2m0xOK-hY{eWFq5=$;b1|`kk*OA)m%;?Qb#&4d1 zuwFz-sdAgadu@#qE9Y~&%%G$cH80mIx6cb|GtDq*8_*GPV5>;$5t9w9f(|~~(V!u3 z{1lOsPn?n)ZPYYka1jkguSHr`{)aJP93xMXI9fXlS~~4rqCpJ-HJO-ve5z#KUgi?B z8l;`1CWX@=QHZ|pfgX+FZzi+?sjO?c!)^(Bz>4=%*LAOl{`j_Eg&S{mDH+U3oz!$t tN7#5}tPJlTUtRZ7X9$>cX%I@@!wr@k`G?~ZjkT7rU|VhsxNZ2@^S?Q|FW>DjgWh(8{`}@uM6&;EdJNcsie{?2k?;-GPr>IBj?kHdrEOF=AAxi6aVW z3}Y5q;w-Qv6FHN#F$zgpLxD%_>U8SqU#5bT&KTE{OD(FT402_JO~eg)O^&>v6r$ul zdPHTFBWy*AF-2*Wt?R{N0e&t7f0 + + + ] [--version ] + +// capture snippet from a binary identified by offset +// manager capture snippet --binary --offset --length + +func New() (*cobra.Command, error) { + cfgP, err := config.Read() + if err != nil { + return nil, err + } + + cfg := *cfgP + + root := commands.Root(cfg) + + root.AddCommand( + commands.List(cfg), + commands.Download(cfg), + commands.AddSnippet(cfg), + commands.WriteSnippet(cfg), + ) + + return root, nil +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/add_snippet.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/add_snippet.go new file mode 100644 index 00000000000..0abf3d49a85 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/add_snippet.go @@ -0,0 +1,108 @@ +package commands + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" + "github.com/anmitsu/go-shlex" + "github.com/spf13/cobra" + "os" + "os/exec" + "strings" +) + +func AddSnippet(appConfig config.Application) *cobra.Command { + var binaryPath, searchPattern string + var length, prefixLength int + + cmd := &cobra.Command{ + Use: "add-snippet", + Short: "capture snippets from binaries", + Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, args []string) error { + + candidates, err := internal.ListAllBinaries(appConfig) + if err != nil { + return fmt.Errorf("unable to list binaries: %w", err) + } + + // launch the UI to pick a path + var binaryPaths []string + for _, k := range internal.NewLogicalEntryKeys(candidates) { + info := candidates[k] + if info.BinaryPath != "" { + binaryPaths = append(binaryPaths, info.BinaryPath) + } + } + + binaryPath, err = ui.PromptSelectBinary(binaryPaths) + if err != nil { + return err + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, version, _, err := inferInfoFromBinaryPath(appConfig, binaryPath) + if err != nil { + return fmt.Errorf("unable to infer name and version from binary path: %w", err) + } + + if searchPattern == "" { + searchPattern = strings.ReplaceAll(version, ".", `\\.`) + } + + return runAddSnippet(binaryPath, name, version, searchPattern, length, prefixLength) + }, + } + + cmd.Flags().StringVar(&searchPattern, "search-for", "", "the pattern to search for in the binary (defaults to the version)") + cmd.Flags().IntVar(&length, "length", 100, "the length of the snippet to capture") + cmd.Flags().IntVar(&prefixLength, "prefix-length", 10, "number of bytes before the search hit to capture") + + return cmd +} + +func runAddSnippet(binaryPath, name, version, searchPattern string, length, prefixLength int) error { + // invoke ./capture-snippet.sh [--search-for ] [--length ] [--prefix-length ]" + + cmd := exec.Command("./capture-snippet.sh", binaryPath, version) + + var args []string + if searchPattern != "" { + args = append(args, "--search-for", searchPattern) + } + if name != "" { + args = append(args, "--group", name) + } + if length > 0 { + args = append(args, fmt.Sprintf("--length %d", length)) + } + if prefixLength > 0 { + args = append(args, fmt.Sprintf("--prefix-length %d", prefixLength)) + } + + var err error + args, err = shlex.Split(strings.Join(args, " "), true) + if err != nil { + return fmt.Errorf("failed to parse arguments: %w", err) + } + cmd.Args = append(cmd.Args, args...) + + fmt.Printf("running: %s\n", strings.Join(cmd.Args, " ")) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start command: %w", err) + } + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("command execution failed: %w", err) + } + + return nil +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/download.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/download.go new file mode 100644 index 00000000000..0230a72531d --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/download.go @@ -0,0 +1,78 @@ +package commands + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/spf13/cobra" +) + +func Download(appConfig config.Application) *cobra.Command { + var configs []config.BinaryFromImage + + var skipSnippets bool + + cmd := &cobra.Command{ + Use: "download", + Short: "download binaries [name@version ...]", + PreRunE: func(cmd *cobra.Command, args []string) error { + + if len(args) > 0 { + for _, arg := range args { + binaryFromImageCfg := appConfig.GetBinaryFromImage(arg, "") + if binaryFromImageCfg == nil { + return fmt.Errorf("no config found for %q", arg) + } + configs = append(configs, *binaryFromImageCfg) + } + } else { + configs = appConfig.FromImages + } + + if skipSnippets { + var err error + configs, err = configsWithoutSnippets(appConfig, configs) + if err != nil { + return err + } + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + for _, binaryFromImageCfg := range configs { + if err := internal.DownloadFromImage(appConfig.DownloadPath, binaryFromImageCfg); err != nil { + return err + } + } + + if len(configs) == 0 { + fmt.Println("no binaries to download") + } + + return nil + }, + } + + cmd.Flags().BoolVarP(&skipSnippets, "skip-if-covered-by-snippet", "s", false, "skip downloading entries already covered by snippets") + + return cmd +} + +func configsWithoutSnippets(appConfig config.Application, configs []config.BinaryFromImage) ([]config.BinaryFromImage, error) { + entries, err := internal.ListAllEntries(appConfig) + if err != nil { + return nil, err + } + + var filtered []config.BinaryFromImage + + for _, cfg := range configs { + if entries.BinaryFromImageHasSnippet(cfg) { + continue + } + filtered = append(filtered, cfg) + } + + return filtered, nil +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go new file mode 100644 index 00000000000..82b12716760 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go @@ -0,0 +1,91 @@ +package commands + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "strings" +) + +func List(appConfig config.Application) *cobra.Command { + + var showPaths bool + + cmd := &cobra.Command{ + Use: "list", + Short: "list managed binaries and managed/unmanaged snippets", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + return runList(appConfig, showPaths) + }, + } + + cmd.Flags().BoolVarP(&showPaths, "show-paths", "p", false, "show paths to binaries and snippets") + + return cmd +} + +func runList(appConfig config.Application, showPaths bool) error { + + material, err := internal.ListAllEntries(appConfig) + if err != nil { + return err + } + + report := renderCatalogerListTable(material, showPaths) + + fmt.Println(report) + + return nil +} + +func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.EntryInfo, showPaths bool) string { + t := table.NewWriter() + t.SetStyle(table.StyleLight) + t.AppendHeader(table.Row{"Group", "Version", "Platform", "Name", "Configured?", "Binary", "Snippet"}) + + keys := internal.NewLogicalEntryKeys(material) + + for _, k := range keys { + info := material[k] + + isConfigured := "" + if info.IsConfigured { + isConfigured = "yes" + } + + bin := "" + snippet := "" + if showPaths { + bin = info.BinaryPath + snippet = info.SnippetPath + } else { + if info.BinaryPath != "" { + bin = "yes" + } + + if info.SnippetPath != "" { + snippet = "yes" + } + } + + t.AppendRow(table.Row{ + k.OrgName, + k.Version, + displayPlatform(k.Platform), + k.Filename, + isConfigured, + bin, + snippet, + }) + } + + return t.Render() +} + +func displayPlatform(platform string) string { + return strings.ReplaceAll(platform, "-", "/") +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go new file mode 100644 index 00000000000..b240868e99c --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go @@ -0,0 +1,13 @@ +package commands + +import ( + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/spf13/cobra" +) + +func Root(_ config.Application) *cobra.Command { + return &cobra.Command{ + Use: "manager", + Short: "manager is a tool for managing binaries and snippets", + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go new file mode 100644 index 00000000000..a0880455cf4 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go @@ -0,0 +1,256 @@ +package commands + +import ( + "debug/elf" + "debug/macho" + "debug/pe" + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + "io" + "os" + "path/filepath" + "strings" +) + +func WriteSnippet(appConfig config.Application) *cobra.Command { + var offset, length int + var name, version string + var binaryPath string + + cmd := &cobra.Command{ + Use: "write-snippet [binary]", + Short: "capture snippets from binaries", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 && (name != "" || version != "") { + return fmt.Errorf("cannot provide name or version without a binary path") + } + + binaryPath = args[0] + if _, err := os.Stat(binaryPath); err != nil { + return fmt.Errorf("unable to stat %q: %w", binaryPath, err) + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + platform, err := getPlatform(binaryPath) + if err != nil { + return fmt.Errorf("unable to get platform: %w", err) + } + + snippetPath, err := getSnippetPath(appConfig, binaryPath, name, version, platform) + if err != nil { + return fmt.Errorf("unable to get snippet path: %w", err) + } + + return runWriteSnippet(binaryPath, offset, length, snippetPath) + }, + } + + cmd.Flags().IntVar(&offset, "offset", -1, "the offset in the binary to start the snippet") + cmd.Flags().IntVar(&length, "length", 100, "the length of the snippet to capture") + cmd.Flags().StringVar(&name, "name", "", "the name of the snippet") + cmd.Flags().StringVar(&version, "version", "", "the version of the snippet") + + return cmd +} + +func runWriteSnippet(binaryPath string, offset, length int, snippetPath string) error { + f, err := os.Open(binaryPath) + if err != nil { + return fmt.Errorf("unable to open binary %q: %w", binaryPath, err) + } + + n, err := f.Seek(int64(offset), io.SeekStart) + if err != nil { + return fmt.Errorf("unable to seek to offset %d: %w", offset, err) + } + + if n != int64(offset) { + return fmt.Errorf("unexpectd to seek value: %d != %d", offset, n) + } + + buf := make([]byte, length) + n2, err := f.Read(buf) + if err != nil { + return fmt.Errorf("unable to read %d bytes: %w", length, err) + } + + if n2 != length { + return fmt.Errorf("unexpected read length: %d != %d", length, n2) + } + + fileDigest, err := internal.Sha256SumFile(f) + if err != nil { + return err + } + + metadata := internal.SnippetMetadata{ + Name: filepath.Base(binaryPath), + Offset: offset, + Length: length, + SnippetSha256: internal.Sha256SumBytes(buf), + FileSha256: fileDigest, + } + + metadataBytes, err := yaml.Marshal(metadata) + if err != nil { + return fmt.Errorf("unable to marshal metadata: %w", err) + } + + splitter := []byte(fmt.Sprintf("\n### byte snippet to follow ###\n")) + + var finalBuf []byte + finalBuf = append(finalBuf, metadataBytes...) + finalBuf = append(finalBuf, splitter...) + finalBuf = append(finalBuf, buf...) + + if err := os.MkdirAll(filepath.Dir(snippetPath), 0755); err != nil { + return fmt.Errorf("unable to create destination directory: %w", err) + } + + if err := os.WriteFile(snippetPath, finalBuf, 0644); err != nil { + return fmt.Errorf("unable to write snippet: %w", err) + } + + fmt.Printf("wrote snippet to %q\n", snippetPath) + + return nil +} + +func getSnippetPath(appConfig config.Application, binaryPath string, name, version, platform string) (string, error) { + binFilename := filepath.Base(binaryPath) + platform = config.PlatformAsValue(platform) + + // if all values provided, use them + if name != "" && version != "" && platform != "" { + return filepath.Join(appConfig.SnippetPath, name, version, platform, binFilename), nil + } + + // otherwise, try to infer them from the existing binary path + name, version, platform, err := inferInfoFromBinaryPath(appConfig, binaryPath) + if err != nil { + return "", err + } + + return filepath.Join(appConfig.SnippetPath, name, version, platform, binFilename), nil +} + +func inferInfoFromBinaryPath(appConfig config.Application, binaryPath string) (string, string, string, error) { + relativePath, err := filepath.Rel(appConfig.DownloadPath, binaryPath) + if err != nil { + return "", "", "", fmt.Errorf("unable to get relative path: %w", err) + } + + // otherwise, try to infer them from the existing binary path + items := internal.SplitFilepath(relativePath) + if len(items) != 4 { + return "", "", "", fmt.Errorf("too few fields: %q", binaryPath) + } + + name := items[0] + version := items[1] + platform := items[2] + + return name, version, platform, nil +} + +// getPlatform will return - for the given binary path, where os can be "linux", "darwin", "windows", +// and arch can be "amd64", "arm64", "arm", etc. +func getPlatform(binaryPath string) (string, error) { + f, err := os.Open(binaryPath) + if err != nil { + return "", fmt.Errorf("unable to open binary %q: %w", binaryPath, err) + } + + elfPlatform := getPlatformElf(f) + if elfPlatform != "" { + return elfPlatform, nil + } + + macPlatform := getPlatformMac(f) + if macPlatform != "" { + return macPlatform, nil + } + + winPlatform := getPlatformWindows(f) + if winPlatform != "" { + return winPlatform, nil + } + + // attempt to infer from the path. It is possible to see invalid-looking binaries that are still something + // we'd like to detect. + items := internal.SplitFilepath(binaryPath) + if len(items) > 2 { + candidate := items[len(items)-2] + if strings.Contains(candidate, "linux") || strings.Contains(candidate, "darwin") || strings.Contains(candidate, "windows") { + return candidate, nil + } + } + + return "", fmt.Errorf("unable to determine platform for %q", binaryPath) +} + +func getPlatformElf(f *os.File) string { + elfFile, err := elf.NewFile(f) + if err != nil { + return "" + } + + var arch string + switch elfFile.Machine { + case elf.EM_X86_64: + arch = "amd64" + case elf.EM_AARCH64: + arch = "arm64" + // TODO... + default: + arch = fmt.Sprintf("unknown-%x", elfFile.Machine) + } + + return fmt.Sprintf("linux-%s", arch) +} + +func getPlatformMac(f *os.File) string { + machoFile, err := macho.NewFile(f) + if err != nil { + return "" + } + + var arch string + switch machoFile.Cpu { + case macho.CpuAmd64: + arch = "amd64" + case macho.CpuArm64: + arch = "arm64" + // TODO... + default: + arch = fmt.Sprintf("unknown-%x", machoFile.Cpu) + } + + return fmt.Sprintf("darwin-%s", arch) +} + +func getPlatformWindows(f *os.File) string { + peFile, err := pe.NewFile(f) + if err != nil { + return "" + } + + var arch string + switch peFile.Machine { + case pe.IMAGE_FILE_MACHINE_AMD64: + arch = "amd64" + case pe.IMAGE_FILE_MACHINE_ARM64: + arch = "arm64" + // TODO... + default: + arch = fmt.Sprintf("unknown-%x", peFile.Machine) + } + + return fmt.Sprintf("windows-%s", arch) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go new file mode 100644 index 00000000000..54473351a79 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go @@ -0,0 +1,139 @@ +package config + +import ( + "fmt" + "github.com/hashicorp/go-multierror" + "github.com/scylladb/go-set/strset" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + "strings" +) + +const Path = "config.yaml" + +type Application struct { + DownloadPath string `yaml:"download-path"` + SnippetPath string `yaml:"snippet-path"` + FromImages []BinaryFromImage `yaml:"from-images"` +} + +func DefaultApplication() Application { + return Application{ + DownloadPath: "bin", + } +} + +func Read() (*Application, error) { + return read(Path) +} + +func read(path string) (*Application, error) { + appConfig := DefaultApplication() + + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + err = yaml.Unmarshal(data, &appConfig) + if err != nil { + return nil, err + } + + if err := appConfig.Validate(); err != nil { + return nil, err + } + return &appConfig, nil +} + +func (c Image) Key() string { + return fmt.Sprintf("%s:%s", c.Reference, c.Platform) +} + +func (c Application) Validate() error { + set := strset.New() + var err error + for i, entry := range c.FromImages { + key := entry.Key() + + if set.Has(key) { + err = multierror.Append(err, fmt.Errorf("duplicate entry %q", entry)) + continue + } + + set.Add(entry.Key()) + + if len(entry.PathsInImage) > 1 && entry.GenericName == "" { + err = multierror.Append(err, fmt.Errorf("specified multiple paths but no name for entry %d (%s)", i+1, key)) + } + if entry.Name() == "" { + err = multierror.Append(err, fmt.Errorf("missing name for entry %d", i+1)) + } + if entry.Version == "" { + err = multierror.Append(err, fmt.Errorf("missing version for entry %d", i+1)) + } + if len(entry.Images) == 0 { + err = multierror.Append(err, fmt.Errorf("missing images for entry %d (%s)", i+1, key)) + } + + var imageSet = strset.New() + for j, image := range entry.Images { + imgKey := image.Key() + if imageSet.Has(imgKey) { + err = multierror.Append(err, fmt.Errorf("duplicate image %q for entry %d (%s)", image.Key(), i+1, key)) + continue + } + imageSet.Add(imgKey) + + if image.Reference == "" { + err = multierror.Append(err, fmt.Errorf("missing ref reference for entry %d (%s) image %d", i+1, key, j+1)) + } + if image.Platform == "" { + err = multierror.Append(err, fmt.Errorf("missing platform for entry %d (%s) image %d", i+1, key, j+1)) + } + } + if len(entry.PathsInImage) == 0 { + err = multierror.Append(err, fmt.Errorf("missing paths for entry %d (%s)", i+1, key)) + } + + } + return err +} + +func (c Application) GetBinaryFromImage(name, version string) *BinaryFromImage { + if strings.Contains(name, "@") && version == "" { + parts := strings.Split(name, "@") + name = parts[0] + version = parts[1] + } + for _, entry := range c.FromImages { + if entry.Name() == name && entry.Version == version { + return &entry + } + } + return nil +} + +func (c Application) GetBinaryFromImageByPath(storePath string) *BinaryFromImage { + // each key is the store path except for the root (e.g. bin or snippet) + entryByStorePath := make(map[string]BinaryFromImage) + + for _, entry := range c.FromImages { + for _, path := range entry.AllStorePaths(c.DownloadPath) { + pathWithoutRoot := splitFilepath(path)[1:] + entryByStorePath[filepath.Join(pathWithoutRoot...)] = entry + } + } + + pathWithoutRoot := filepath.Join(splitFilepath(storePath)[1:]...) + if entry, ok := entryByStorePath[pathWithoutRoot]; ok { + return &entry + } + + return nil +} + +func splitFilepath(path string) []string { + return strings.Split(path, string(filepath.Separator)) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go new file mode 100644 index 00000000000..ea873707d27 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go @@ -0,0 +1,79 @@ +package config + +import ( + "crypto/sha256" + "fmt" + "gopkg.in/yaml.v3" + "path/filepath" + "strings" +) + +type BinaryFromImage struct { + GenericName string `yaml:"name"` + Version string `yaml:"version"` + + Images []Image `yaml:"images"` + PathsInImage []string `yaml:"paths"` +} + +type Image struct { + Reference string `yaml:"ref"` + Platform string `yaml:"platform"` +} + +func (c BinaryFromImage) Key() string { + return fmt.Sprintf("%s:%s", c.Name(), c.Version) +} + +func (c BinaryFromImage) Name() string { + displayName := c.GenericName + if displayName == "" { + var path string + if len(c.PathsInImage) > 0 { + path = c.PathsInImage[0] + } + if path == "" { + return "" + } + return filepath.Base(path) + } + return displayName +} + +func (c BinaryFromImage) AllStorePaths(dest string) []string { + var paths []string + for _, image := range c.Images { + paths = append(paths, c.AllStorePathsForImage(image, dest)...) + } + return paths +} + +func (c BinaryFromImage) AllStorePathsForImage(image Image, dest string) []string { + var paths []string + + platform := PlatformAsValue(image.Platform) + for _, path := range c.PathsInImage { + base := filepath.Base(path) + if path == "" { + base = "" + } + paths = append(paths, filepath.Join(dest, c.Name(), c.Version, platform, base)) + } + + return paths +} + +func PlatformAsValue(platform string) string { + return strings.ReplaceAll(platform, "/", "-") +} + +func (c BinaryFromImage) Fingerprint() string { + by, err := yaml.Marshal(c) + if err != nil { + panic(err) + } + + hasher := sha256.New() + hasher.Write(by) + return fmt.Sprintf("%x", hasher.Sum(nil)) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go new file mode 100644 index 00000000000..e64880be624 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go @@ -0,0 +1,157 @@ +package internal + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" + "github.com/google/uuid" + "os" + "os/exec" + "path/filepath" +) + +func DownloadFromImage(dest string, config config.BinaryFromImage) error { + t := ui.Title{Name: config.Name(), Version: config.Version} + t.Start() + + hostPaths := config.AllStorePaths(dest) + if allPathsExist(hostPaths) { + if !isDownloadStale(config, hostPaths) { + t.Skip("already exists") + return nil + } else { + t.Update("stale, updating...") + } + } + + if err := pullDockerImages(config.Images); err != nil { + return err + } + + if err := copyBinariesFromDockerImages(config, dest); err != nil { + return fmt.Errorf("failed to copy binary for %s@%s: %v", config.Name(), config.Version, err) + } + + return nil +} + +func isDownloadStale(config config.BinaryFromImage, binaryPaths []string) bool { + currentFingerprint := config.Fingerprint() + + for _, path := range binaryPaths { + fingerprintPath := path + ".fingerprint" + if _, err := os.Stat(fingerprintPath); err != nil { + // missing a fingerprint file means the download is stale + return true + } + + writtenFingerprint, err := os.ReadFile(fingerprintPath) + if err != nil { + // missing a fingerprint file means the download is stale + return true + } + + if string(writtenFingerprint) != currentFingerprint { + // the fingerprint file does not match the current fingerprint, so the download is stale + return true + } + } + + return false +} + +func allPathsExist(paths []string) bool { + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + return false + } + } + return true +} + +func pullDockerImages(images []config.Image) error { + for _, image := range images { + if err := pullDockerImage(image.Reference, image.Platform); err != nil { + return fmt.Errorf("failed to pull image %s for platform %s: %v", image.Reference, image.Platform, err) + } + } + return nil +} + +func pullDockerImage(imageReference, platform string) error { + a := ui.Action{Msg: fmt.Sprintf("pull image %s (%s)", imageReference, platform)} + a.Start() + cmd := exec.Command("docker", "image", "inspect", imageReference) + if err := cmd.Run(); err == nil { + a.Skip(fmt.Sprintf("docker image already exists %q", imageReference)) + + return nil + } + + cmd = exec.Command("docker", "pull", "--platform", platform, imageReference) + err := cmd.Run() + + a.Done(err) + + return err +} + +func copyBinariesFromDockerImages(config config.BinaryFromImage, destination string) (err error) { + for _, image := range config.Images { + if err := copyBinariesFromDockerImage(config, destination, image); err != nil { + return err + } + } + + return nil +} + +func copyBinariesFromDockerImage(config config.BinaryFromImage, destination string, image config.Image) (err error) { + containerName := fmt.Sprintf("%s-%s-%s", config.Name(), config.Version, uuid.New().String()) + + cmd := exec.Command("docker", "create", "--name", containerName, image.Reference) + if err = cmd.Run(); err != nil { + return err + } + + defer func() { + cmd := exec.Command("docker", "rm", containerName) + cmd.Run() + }() + + for i, destinationPath := range config.AllStorePathsForImage(image, destination) { + path := config.PathsInImage[i] + if err := copyBinaryFromContainer(containerName, path, destinationPath, config.Fingerprint()); err != nil { + return err + } + } + + return nil +} + +func copyBinaryFromContainer(containerName, containerPath, destinationPath, fingerprint string) (err error) { + + a := ui.Action{Msg: fmt.Sprintf("extract %s", containerPath)} + a.Start() + + defer func() { + a.Done(err) + }() + + if err := os.MkdirAll(filepath.Dir(destinationPath), 0755); err != nil { + return err + } + + cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", containerName, containerPath), destinationPath) + if err := cmd.Run(); err != nil { + return err + } + + // capture fingerprint file + fingerprintPath := destinationPath + ".fingerprint" + if err := os.WriteFile(fingerprintPath, []byte(fingerprint), 0644); err != nil { + return fmt.Errorf("unable to write fingerprint file: %w", err) + } + + return nil +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go new file mode 100644 index 00000000000..f07fc5dc0e2 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go @@ -0,0 +1,197 @@ +package internal + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "os" + "path/filepath" + "sort" + "strings" +) + +type Entries map[LogicalEntryKey]EntryInfo + +type EntryInfo struct { + IsConfigured bool + BinaryPath string + SnippetPath string +} + +type LogicalEntryKey struct { + OrgName string + Version string + Platform string + Filename string +} + +func (k LogicalEntryKey) Path() string { + return fmt.Sprintf("%s/%s/%s/%s", k.OrgName, k.Version, k.Platform, k.Filename) +} + +type LogicalEntryKeys []LogicalEntryKey + +func (l LogicalEntryKeys) Len() int { + return len(l) +} + +func (l LogicalEntryKeys) Less(i, j int) bool { + if l[i].OrgName == l[j].OrgName { + if l[i].Version == l[j].Version { + if l[i].Platform == l[j].Platform { + return l[i].Filename < l[j].Filename + } + return l[i].Platform < l[j].Platform + } + return l[i].Version < l[j].Version + } + return l[i].OrgName < l[j].OrgName +} + +func (l LogicalEntryKeys) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func NewLogicalEntryKeys(m map[LogicalEntryKey]EntryInfo) LogicalEntryKeys { + var keys LogicalEntryKeys + for k := range m { + keys = append(keys, k) + } + sort.Sort(keys) + return keys +} + +func ListAllBinaries(appConfig config.Application) (Entries, error) { + binaries, err := allFilePaths(appConfig.DownloadPath) + if err != nil { + return nil, fmt.Errorf("unable to list binaries: %w", err) + } + + cases := make(map[LogicalEntryKey]EntryInfo) + for _, storePath := range binaries { + isConfigured := appConfig.GetBinaryFromImageByPath(storePath) != nil + + relativePath, err := filepath.Rel(appConfig.DownloadPath, storePath) + if err != nil { + return nil, fmt.Errorf("unable to get relative path for %q: %w", storePath, err) + } + + key, err := getLogicalKey(relativePath) + if err != nil { + return nil, fmt.Errorf("unable to get logical key for binary %q: %w", storePath, err) + } + cases[*key] = EntryInfo{ + IsConfigured: isConfigured, + BinaryPath: storePath, + } + } + + return cases, nil +} + +func ListAllEntries(appConfig config.Application) (Entries, error) { + + snippets, err := allFilePaths(appConfig.SnippetPath) + if err != nil { + return nil, fmt.Errorf("unable to list snippets: %w", err) + } + + cases, err := ListAllBinaries(appConfig) + if err != nil { + return nil, fmt.Errorf("unable to list binaries: %w", err) + } + + // anything configured that isn't in the binaries list? + for _, cfg := range appConfig.FromImages { + for _, image := range cfg.Images { + for _, path := range cfg.AllStorePathsForImage(image, appConfig.DownloadPath) { + key := newLogicalEntryForImage(cfg, image, path) + if _, ok := cases[key]; ok { + continue + } + cases[key] = EntryInfo{ + IsConfigured: true, + } + } + } + } + + // correlate snippets to existing binaries and configurations (and add unmanaged ones) + for _, storePath := range snippets { + relativePath, err := filepath.Rel(appConfig.SnippetPath, storePath) + if err != nil { + return nil, fmt.Errorf("unable to get relative path for %q: %w", storePath, err) + } + key, err := getLogicalKey(relativePath) + if err != nil { + return nil, fmt.Errorf("unable to get logical key for snippet %q: %w", storePath, err) + } + + if v, ok := cases[*key]; ok { + v.SnippetPath = storePath + cases[*key] = v + + continue + } + + cases[*key] = EntryInfo{ + IsConfigured: false, + SnippetPath: storePath, + } + } + + return cases, nil +} + +func newLogicalEntryForImage(cfg config.BinaryFromImage, image config.Image, storePath string) LogicalEntryKey { + return LogicalEntryKey{ + OrgName: cfg.Name(), + Version: cfg.Version, + Platform: config.PlatformAsValue(image.Platform), + Filename: filepath.Base(storePath), + } +} + +func getLogicalKey(managedBinaryPath string) (*LogicalEntryKey, error) { + // infer the logical key from the path alone: name/version/platform/filename + + items := SplitFilepath(managedBinaryPath) + if len(items) != 4 { + return nil, fmt.Errorf("invalid managed binary path: %q", managedBinaryPath) + } + + return &LogicalEntryKey{ + OrgName: items[0], + Version: items[1], + Platform: items[2], + Filename: items[3], + }, nil +} + +func allFilePaths(root string) ([]string, error) { + var paths []string + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if info != nil && !info.IsDir() && !strings.HasSuffix(path, ".fingerprint") { + paths = append(paths, path) + } + return nil + }) + if err != nil { + return nil, err + } + return paths, nil +} + +func (e Entries) BinaryFromImageHasSnippet(cfg config.BinaryFromImage) bool { + // all paths for all images must have snippets to return true + for _, image := range cfg.Images { + for _, storePath := range cfg.AllStorePathsForImage(image, "") { + key := newLogicalEntryForImage(cfg, image, storePath) + if v, ok := e[key]; ok { + if v.SnippetPath == "" { + return false + } + } + } + } + return true +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go new file mode 100644 index 00000000000..171db4a3bdb --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go @@ -0,0 +1,39 @@ +package internal + +import ( + "fmt" + "gopkg.in/yaml.v3" + "os" + "strings" +) + +type SnippetMetadata struct { + Name string `yaml:"name"` + Offset int `yaml:"offset"` + Length int `yaml:"length"` + SnippetSha256 string `yaml:"snippetSha256"` + FileSha256 string `yaml:"fileSha256"` +} + +func ReadSnippetMetadata(path string) (*SnippetMetadata, error) { + if path == "" { + return nil, nil + } + + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + fields := strings.Split(string(contents), "\n### byte snippet to follow ###\n") + if len(fields) != 2 { + return nil, fmt.Errorf("this is not a snippet") + } + + var metadata SnippetMetadata + if err := yaml.Unmarshal([]byte(fields[0]), &metadata); err != nil { + return nil, err + } + + return &metadata, nil +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go new file mode 100644 index 00000000000..00d6381e845 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go @@ -0,0 +1,51 @@ +package ui + +import ( + "errors" + "fmt" + "os/exec" + "strings" +) + +type Action struct { + Msg string +} + +func (a Action) Start() { + fmt.Printf(" • %s%s%s\n", purple+italic, a.Msg, reset) +} + +func (a Action) Skip(newMsg ...string) { + if len(newMsg) > 0 { + // clear the line + goToPreviousLineStart() + // add a little extra to account for ansi escape codes (hack) + fmt.Printf("%s\n", strings.Repeat(" ", len(a.Msg)+10)) + a.Msg = newMsg[0] + } + goToPreviousLineStart() + formatSkip(a.Msg) +} + +func (a Action) Done(err error) { + goToPreviousLineStart() + if err != nil { + + fmt.Printf(" %s✗%s %s%s%s\n", red+bold, reset, red, a.Msg, reset) + + var exitError *exec.ExitError + if errors.As(err, &exitError) && len(exitError.Stderr) > 0 { + fmt.Printf(" %s├──%s %s%s%s\n", grey, reset, red, err, reset) + fmt.Printf(" %s└──%s %s%s%s\n", grey, reset, red, "stderr:", reset) + fmt.Println(string(exitError.Stderr)) + } else { + fmt.Printf(" %s└──%s %s%s%s\n", grey, reset, red, err, reset) + } + return + } + fmt.Printf(" %s✔%s %s\n", green+bold, reset, a.Msg) +} + +func formatSkip(msg string) { + fmt.Printf(" %sâ­%s %s%s%s\n", bold, reset, grey, msg, reset) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/ansi.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/ansi.go new file mode 100644 index 00000000000..6be89faa4de --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/ansi.go @@ -0,0 +1,17 @@ +package ui + +import "fmt" + +const ( + grey = "\033[90m" + reset = "\033[0m" + bold = "\033[1m" + red = "\033[31m" + italic = "\033[3m" + purple = "\033[95m" // hi variant + green = "\033[32m" +) + +func goToPreviousLineStart() { + fmt.Printf("\033[F") +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go new file mode 100644 index 00000000000..b551ead9ed1 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go @@ -0,0 +1,107 @@ +package ui + +import ( + "fmt" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "os" +) + +var quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) + +type item string + +func (i item) Title() string { return string(i) } +func (i item) Description() string { return "" } +func (i item) FilterValue() string { return string(i) } + +type model struct { + list list.Model + choice string + quitting bool +} + +func PromptSelectBinary(binaryPaths []string) (string, error) { + var items []list.Item + for _, p := range binaryPaths { + items = append(items, item(p)) + } + + d := list.NewDefaultDelegate() + d.ShowDescription = false + d.Styles.NormalTitle = d.Styles.NormalTitle.PaddingLeft(4) + d.Styles.SelectedTitle = d.Styles.SelectedTitle.PaddingLeft(3) + d.SetSpacing(0) + + l := list.New(items, d, 80, 80) + l.Title = "Select a binary to capture a snippet from:" + l.SetShowStatusBar(false) + l.SetFilteringEnabled(true) + l.Styles.Title = lipgloss.NewStyle().Bold(true).MarginLeft(1) + l.Styles.PaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) + l.Styles.HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) + + m := model{list: l} + + p := tea.NewProgram(m, tea.WithAltScreen()) + + fm, err := p.Run() + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + m = fm.(model) + + if m.quitting { + return "", fmt.Errorf("cancelled") + } + + if m.choice == "" { + return "", fmt.Errorf("no binary selected") + } + + return m.choice, nil +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.list.SetWidth(msg.Width) + m.list.SetHeight(msg.Height) + return m, nil + + case tea.KeyMsg: + switch keypress := msg.String(); keypress { + case "ctrl+c": + m.quitting = true + return m, tea.Quit + + case "enter": + i, ok := m.list.SelectedItem().(item) + if ok { + m.choice = string(i) + } + return m, tea.Quit + } + } + + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + return m, cmd +} + +func (m model) View() string { + if m.choice != "" { + return quitTextStyle.Render(fmt.Sprintf("Selected %q", m.choice)) + } + if m.quitting { + return quitTextStyle.Render("Cancelled") + } + return "\n" + m.list.View() +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/error.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/error.go new file mode 100644 index 00000000000..c107ad34ad7 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/error.go @@ -0,0 +1,7 @@ +package ui + +import "fmt" + +func RenderError(err error) string { + return fmt.Sprintf("%s%v%s", red, err, reset) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/title.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/title.go new file mode 100644 index 00000000000..9da59b14a99 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/title.go @@ -0,0 +1,33 @@ +package ui + +import ( + "fmt" + "strings" +) + +type Title struct { + Name, Version string +} + +func (t Title) Start() { + t.start() + fmt.Println() +} + +func (t Title) start() { + fmt.Printf("%s%s@%s%s", bold, t.Name, t.Version, reset) +} + +func (t Title) Update(msg string) { + goToPreviousLineStart() + t.start() + fmt.Print(strings.Repeat(" ", 35-(len(t.Name)+len(t.Version)))) + fmt.Printf(" %sâš %s %s%s%s\n", bold, reset, italic+grey, msg, reset) +} + +func (t Title) Skip(msg string) { + goToPreviousLineStart() + t.start() + fmt.Print(strings.Repeat(" ", 35-(len(t.Name)+len(t.Version)))) + formatSkip(msg) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go new file mode 100644 index 00000000000..ab2578f8057 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go @@ -0,0 +1,34 @@ +package internal + +import ( + "crypto/sha256" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +func SplitFilepath(path string) []string { + return strings.Split(path, string(filepath.Separator)) +} + +func Sha256SumFile(f *os.File) (string, error) { + _, err := f.Seek(0, io.SeekStart) + if err != nil { + return "", fmt.Errorf("unable to seek to start of file: %w", err) + } + hasher := sha256.New() + _, err = io.Copy(hasher, f) + if err != nil { + return "", fmt.Errorf("unable to hash file: %w", err) + } + + return fmt.Sprintf("%x", hasher.Sum(nil)), nil +} + +func Sha256SumBytes(buf []byte) string { + hasher := sha256.New() + hasher.Write(buf) + return fmt.Sprintf("%x", hasher.Sum(nil)) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/main.go b/syft/pkg/cataloger/binary/test-fixtures/manager/main.go new file mode 100644 index 00000000000..d6b35fe3fcd --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" + "os" +) + +func main() { + cmd, err := cli.New() + if err != nil { + exit(err) + } + + if err := cmd.Execute(); err != nil { + exit(err) + } +} + +func exit(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", ui.RenderError(err)) + } + os.Exit(1) +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go new file mode 100644 index 00000000000..a95df4f6cfa --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go @@ -0,0 +1,100 @@ +package testutil + +import ( + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "testing" +) + +// SnippetOrBinary returns the path to either the binary or the snippet for the given logical entry key. +// Note: this is intended to be used only within the context of the binary cataloger test fixtures. Any other +// use is unsupported. Path should be a logical path relative to the test-fixtures/classifiers directory (but does +// not specify the "bin" or "snippets" parent path... this is determined logically [snippets > binary unless told +// otherwise]). Path should also be to the directory containing the binary or snippets of interest (not the binaries +// or snippets itself). +func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { + t.Helper() + + require.Len(t, internal.SplitFilepath(path), 3, "path must be a in the form //") + + // cd to test-fixtures directory and load the config + + cwd, err := os.Getwd() + require.NoError(t, err) + + require.NoError(t, os.Chdir("test-fixtures")) + defer func() { + require.NoError(t, os.Chdir(cwd)) + }() + + appConfig, err := config.Read() + require.NoError(t, err) + + // find the first matching fixture path that matches the given requirements + + entries, err := internal.ListAllEntries(*appConfig) + require.NoError(t, err) + + var fixturePath string + for k, v := range entries { + if filepath.Dir(k.Path()) == path { + // prefer the snippet over the binary + if !requireBinary { + if v.SnippetPath != "" { + t.Logf("using snippet for %q", path) + validateSnippet(t, v.BinaryPath, v.SnippetPath) + fixturePath = v.SnippetPath + break + } + if v.BinaryPath != "" { + fixturePath = v.BinaryPath + break + } + t.Fatalf("no binary or snippet found for %q", path) + } + if v.BinaryPath != "" { + t.Logf("forcing the use of the original binary for %q", path) + fixturePath = v.BinaryPath + break + } + + t.Fatalf("no binary found for %q", path) + } + } + + if fixturePath == "" { + t.Fatalf("no fixture found for %q", path) + } + + // this should be relative to the tests-fixtures directory and should be the directory containing the binary or + // snippet of interest (not the path to the binary or snippet itself) + return filepath.Join("test-fixtures", filepath.Dir(fixturePath)) +} + +func validateSnippet(t *testing.T, binaryPath, snippetPath string) { + t.Helper() + + // get a sha256 of the binary + if _, err := os.Stat(binaryPath); err != nil { + // no binary to validate against (this is ok) + return + } + + metadata, err := internal.ReadSnippetMetadata(snippetPath) + require.NoError(t, err) + + if metadata == nil { + return + } + + f, err := os.Open(binaryPath) + require.NoError(t, err) + expected, err := internal.Sha256SumFile(f) + require.NoError(t, err) + + require.Equal(t, expected, metadata.FileSha256, "snippet shadows a binary with a different sha256") + +} From d84971960567eca1f3a403fbd1b3c4e07d3a447d Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 22 Dec 2023 12:48:50 -0500 Subject: [PATCH 19/28] add remaining binary cataloger patterns and snippets Signed-off-by: Alex Goodman --- .github/workflows/validations.yaml | 2 +- syft/pkg/cataloger/binary/cataloger_test.go | 770 +++++++++--------- .../cataloger/binary/test-fixtures/.gitignore | 3 +- .../cataloger/binary/test-fixtures/Makefile | 8 +- .../binary/test-fixtures/capture-snippet.sh | 2 +- .../classifiers/positive/.gitignore | 2 - .../bash | Bin 618 -> 0 bytes .../classifiers/positive/busybox-3.33.3/[ | 3 - .../positive/busybox-3.33.3/busybox | 1 - .../erlexec | Bin 648 -> 0 bytes .../go | Bin 444 -> 0 bytes .../classifiers/positive/go-hint-1.15/VERSION | 1 - .../haproxy | Bin 638 -> 0 bytes .../haproxy | Bin 642 -> 0 bytes .../haproxy | Bin 642 -> 0 bytes .../positive/haproxy-2.7.3/haproxy | Bin 5474 -> 0 bytes .../classifiers/positive/httpd-2.4.54/httpd | Bin 652 -> 0 bytes .../classifiers/positive/ibm-java-1.8.0/java | Bin 115 -> 0 bytes .../positive/mariadb-10.6.15/README.md | 31 - .../positive/mariadb-10.6.15/mariadb | Bin 40 -> 0 bytes .../memcached | 18 - .../positive/mysql-5.6.51/README.md | 39 - .../classifiers/positive/mysql-5.6.51/mysql | Bin 100 -> 0 bytes .../positive/mysql-8.0.34/README.md | 40 - .../classifiers/positive/mysql-8.0.34/mysql | Bin 100 -> 0 bytes .../classifiers/positive/nginx-1.25.1/nginx | Bin 837 -> 0 bytes .../positive/nginx-openresty-1.21.4.2/nginx | Bin 1425 -> 0 bytes .../classifiers/positive/node-19.2.1/node | 2 - .../classifiers/positive/openjdk-1.8.0/java | Bin 39 -> 0 bytes .../positive/openjdk-lts-11.0.17/java | Bin 124 -> 0 bytes .../positive/php-apache-8.2.1/libphp.so | 5 - .../classifiers/positive/php-cli-8.2.1/php | 5 - .../positive/php-fpm-8.2.1/php-fpm | 5 - .../positive/postgresql-15.1/postgres | Bin 16 -> 0 bytes .../positive/postgresql-15beta4/postgres | Bin 19 -> 0 bytes .../positive/postgresql-9.5alpha1/postgres | 5 - .../positive/postgresql-9.6.24/postgres | Bin 18 -> 0 bytes .../positive/python-binary-3.6/python3.6 | Bin 69 -> 0 bytes .../python-binary-lib-3.7/libpython3.7.so | Bin 69 -> 0 bytes .../python-duplicates/libpython3.8.so | Bin 122 -> 0 bytes .../positive/redis-server-2.8.23/redis-server | Bin 7025 -> 0 bytes .../positive/redis-server-4.0.11/redis-server | Bin 9179 -> 0 bytes .../positive/redis-server-5.0.0/redis-server | Bin 7061 -> 0 bytes .../positive/redis-server-6.0.16/redis-server | Bin 2574 -> 0 bytes .../positive/redis-server-7.0.0/redis-server | Bin 1239 -> 0 bytes .../positive/redis-server-7.0.14/redis-server | Bin 890 -> 0 bytes .../redis-server-7.2.3-amd64/redis-server | Bin 831 -> 0 bytes .../redis-server-7.2.3-arm64/redis-server | Bin 816 -> 0 bytes .../classifiers/positive/ruby-1.9.3p551/ruby | 9 - .../lib/libstd-f6f9eec1635e636a.dylib | Bin 21301 -> 0 bytes .../lib/libstd-16f2b65e77054c42.dylib | Bin 28955 -> 0 bytes .../lib/libstd-86aefecbddda356d.so | Bin 2180 -> 0 bytes .../positive/traefik-1.7.34/traefik | Bin 9 -> 0 bytes .../positive/traefik-2.9.6/traefik | Bin 7 -> 0 bytes .../haproxy/2.7.3/linux-amd64/haproxy | Bin 331 -> 331 bytes .../1.8.0_352-b08/linux-amd64/java | Bin 0 -> 325 bytes .../java-jre-openjdk/11.0.17/linux-amd64/java | Bin 0 -> 325 bytes .../java-jre-oracle/19.0.1/darwin}/java | Bin .../java-jre-oracle/19.0.1/linux-amd64}/java | Bin .../perl/5.12.5/linux-amd64}/perl | Bin .../perl/5.20.0/linux-amd64}/perl | Bin .../perl/5.37.8/linux-amd64}/perl | Bin .../snippets/php-cli/8.2.1/linux-amd64/php | 12 + .../php-fpm/8.2.1/linux-amd64/php-fpm | 12 + .../postgres/9.5alpha1/linux-amd64/postgres | 10 + .../3.8.16/linux-amd64}/dir/python3.8 | Bin .../3.8.16/linux-amd64}/patchlevel.h | 0 .../3.8.16/linux-amd64}/python3.8 | Bin .../3.7.4/linux-amd64/libpython3.7m.so.1.0 | Bin 0 -> 344 bytes .../3.5.3/linux-amd64}/python3.5 | Bin .../python/3.6.3/linux-amd64/python3.6 | Bin 0 -> 241 bytes .../snippets/ruby/1.9.3p551/linux-amd64/ruby | Bin 0 -> 328 bytes .../traefik/1.7.34/linux-amd64/traefik | Bin 0 -> 332 bytes .../traefik/2.9.6/linux-amd64/traefik | Bin 0 -> 332 bytes .../binary/test-fixtures/config.yaml | 304 +++---- .../test-fixtures/manager/internal/cli/cli.go | 3 +- .../internal/cli/commands/add_snippet.go | 15 +- .../manager/internal/cli/commands/download.go | 5 +- .../manager/internal/cli/commands/list.go | 19 +- .../manager/internal/cli/commands/root.go | 3 +- .../internal/cli/commands/write_snippet.go | 31 +- .../manager/internal/config/application.go | 8 +- .../internal/config/binary_from_image.go | 3 +- .../manager/internal/download_from_image.go | 66 +- .../manager/internal/list_entries.go | 8 +- .../manager/internal/snippet_metadata.go | 3 +- .../manager/internal/ui/action.go | 1 - .../manager/internal/ui/binary_list.go | 3 +- .../binary/test-fixtures/manager/main.go | 3 +- .../manager/testutil/snippet_or_binary.go | 9 +- 90 files changed, 676 insertions(+), 793 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ delete mode 120000 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22-cc32c297e83f2626d253d8615008c199a1dc9cba13883731f78ecbe9bfc524d4-1386742-100/haproxy delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3-99d01769ac3457cf33b7e3eb93050453e20c09afe5631f810fd658fb5314bbd3-3156502-100/haproxy delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ibm-java-1.8.0/java delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mariadb-10.6.15/README.md delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mariadb-10.6.15/mariadb delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/memcached-1.6.18/memcached-1.6.18-176467412e0722ae1b2c1159555d33574653ebfa87e8591d88c1e6e416ab3019-19-100/memcached delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mysql-5.6.51/README.md delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mysql-5.6.51/mysql delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mysql-8.0.34/README.md delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/mysql-8.0.34/mysql delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-1.25.1/nginx delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2/nginx delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/node-19.2.1/node delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-1.8.0/java delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-lts-11.0.17/java delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15.1/postgres delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.5alpha1/postgres delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.6.24/postgres delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-binary-3.6/python3.6 delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-binary-lib-3.7/libpython3.7.so delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-duplicates/libpython3.8.so delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-4.0.11/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-6.0.16/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.0/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-amd64/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/ruby-1.9.3p551/ruby delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/rust-1.50.0/lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin/lib/libstd-16f2b65e77054c42.dylib delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl/lib/libstd-86aefecbddda356d.so delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-1.7.34/traefik delete mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-2.9.6/traefik create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/1.8.0_352-b08/linux-amd64/java create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/11.0.17/linux-amd64/java rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/oracle-macos-java-19.0.1 => snippets/java-jre-oracle/19.0.1/darwin}/java (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/oracle-java-19.0.1 => snippets/java-jre-oracle/19.0.1/linux-amd64}/java (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/perl-5.12.5 => snippets/perl/5.12.5/linux-amd64}/perl (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/perl-5.20.0 => snippets/perl/5.20.0/linux-amd64}/perl (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/perl-5.37.8 => snippets/perl/5.37.8/linux-amd64}/perl (100%) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-cli/8.2.1/linux-amd64/php create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-fpm/8.2.1/linux-amd64/php-fpm create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/9.5alpha1/linux-amd64/postgres rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/python-duplicates => snippets/python-duplicates/3.8.16/linux-amd64}/dir/python3.8 (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/python-duplicates => snippets/python-duplicates/3.8.16/linux-amd64}/patchlevel.h (100%) rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/python-duplicates => snippets/python-duplicates/3.8.16/linux-amd64}/python3.8 (100%) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/python-shared-lib/3.7.4/linux-amd64/libpython3.7m.so.1.0 rename syft/pkg/cataloger/binary/test-fixtures/classifiers/{positive/python-3.5-with-incorrect-match => snippets/python-with-incorrect-match/3.5.3/linux-amd64}/python3.5 (100%) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/python/3.6.3/linux-amd64/python3.6 create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/ruby/1.9.3p551/linux-amd64/ruby create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/traefik/1.7.34/linux-amd64/traefik create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/traefik/2.9.6/linux-amd64/traefik diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 862244d9bea..cac95826a64 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -57,7 +57,7 @@ jobs: - name: Restore binary cataloger test-fixture cache uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2 with: - path: syft/pkg/cataloger/binary/test-fixtures/classifiers/dynamic + path: syft/pkg/cataloger/binary/test-fixtures/classifiers/bin key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }} - name: Restore Kernel test-fixture cache diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index f576d5d5cbd..3efc0525571 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -4,7 +4,6 @@ import ( "errors" "flag" "fmt" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil" "io" "strings" "testing" @@ -17,12 +16,13 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil" "github.com/anchore/syft/syft/source" ) var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "force the use of binaries for testing (instead of snippets)") -func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { +func Test_Cataloger_PositiveCases(t *testing.T) { tests := []struct { name string logicalFixture string @@ -61,18 +61,19 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("postgresql-binary"), }, }, - // TODO: missing original binary - //{ - // logicalFixture: "postgres/9.5alpha1/linux-amd64", - // expected: pkg.Package{ - // Name: "postgresql", - // Version: "9.5alpha1", - // Type: "binary", - // PURL: "pkg:generic/postgresql@9.5alpha1", - // Locations: locations("postgres"), - // Metadata: metadata("postgresql-binary"), - // }, - //}, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "postgres/9.5alpha1/linux-amd64", + expected: pkg.Package{ + Name: "postgresql", + Version: "9.5alpha1", + Type: "binary", + PURL: "pkg:generic/postgresql@9.5alpha1", + Locations: locations("postgres"), + Metadata: metadata("postgresql-binary"), + }, + }, { logicalFixture: "mysql/8.0.34/linux-amd64", expected: pkg.Package{ @@ -106,29 +107,28 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("mariadb-binary"), }, }, - // TODO: need to add original binary - //{ - // logicalFixture: "traefik/2.9.6/linux-amd64", - // expected: pkg.Package{ - // Name: "traefik", - // Version: "2.9.6", - // Type: "binary", - // PURL: "pkg:generic/traefik@2.9.6", - // Locations: locations("traefik"), - // Metadata: metadata("traefik-binary"), - // }, - //}, - //{ - // logicalFixture: "test-fixtures/classifiers/positive/traefik-1.7.34/linux-amd64", - // expected: pkg.Package{ - // Name: "traefik", - // Version: "1.7.34", - // Type: "binary", - // PURL: "pkg:generic/traefik@1.7.34", - // Locations: locations("traefik"), - // Metadata: metadata("traefik-binary"), - // }, - //}, + { + logicalFixture: "traefik/1.7.34/linux-amd64", + expected: pkg.Package{ + Name: "traefik", + Version: "1.7.34", + Type: "binary", + PURL: "pkg:generic/traefik@1.7.34", + Locations: locations("traefik"), + Metadata: metadata("traefik-binary"), + }, + }, + { + logicalFixture: "traefik/2.9.6/linux-amd64", + expected: pkg.Package{ + Name: "traefik", + Version: "2.9.6", + Type: "binary", + PURL: "pkg:generic/traefik@2.9.6", + Locations: locations("traefik"), + Metadata: metadata("traefik-binary"), + }, + }, { logicalFixture: "memcached/1.6.18/linux-amd64", expected: pkg.Package{ @@ -151,78 +151,84 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("httpd-binary"), }, }, - - // TODO: missing original binaries - //{ - // logicalFixture: "test-fixtures/classifiers/positive/php-cli-8.2.1/linux-amd64", - // expected: pkg.Package{ - // Name: "php-cli", - // Version: "8.2.1", - // Type: "binary", - // PURL: "pkg:generic/php-cli@8.2.1", - // Locations: locations("php"), - // Metadata: metadata("php-cli-binary"), - // }, - //}, - //{ - // logicalFixture: "test-fixtures/classifiers/positive/php-fpm-8.2.1/linux-amd64", - // expected: pkg.Package{ - // Name: "php-fpm", - // Version: "8.2.1", - // Type: "binary", - // PURL: "pkg:generic/php-fpm@8.2.1", - // Locations: locations("php-fpm"), - // Metadata: metadata("php-fpm-binary"), - // }, - //}, - //{ - // logicalFixture: "test-fixtures/classifiers/positive/php-apache-8.2.1/linux-amd64", - // expected: pkg.Package{ - // Name: "libphp", - // Version: "8.2.1", - // Type: "binary", - // PURL: "pkg:generic/php@8.2.1", - // Locations: locations("libphp.so"), - // Metadata: metadata("php-apache-binary"), - // }, - //}, - - // TODO: original binary is different than test fixture - //{ - // logicalFixture: "perl/5.12.5/linux-amd64", - // expected: pkg.Package{ - // Name: "perl", - // Version: "5.12.5", - // Type: "binary", - // PURL: "pkg:generic/perl@5.12.5", - // Locations: locations("perl"), - // Metadata: metadata("perl-binary"), - // }, - //}, - //{ - // name: "positive-perl-5.20.0", - // logicalFixture: "test-fixtures/classifiers/positive/perl-5.20.0/linux-amd64", - // expected: pkg.Package{ - // Name: "perl", - // Version: "5.20.0", - // Type: "binary", - // PURL: "pkg:generic/perl@5.20.0", - // Locations: locations("perl"), - // Metadata: metadata("perl-binary"), - // }, - //}, - //{ - // name: "positive-perl-5.37.8", - // logicalFixture: "test-fixtures/classifiers/positive/perl-5.37.8/linux-amd64", - // expected: pkg.Package{ - // Name: "perl", - // Version: "5.37.8", - // Type: "binary", - // PURL: "pkg:generic/perl@5.37.8", - // Locations: locations("perl"), - // Metadata: metadata("perl-binary"), - // }, - //}, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "php-cli/8.2.1/linux-amd64", + expected: pkg.Package{ + Name: "php-cli", + Version: "8.2.1", + Type: "binary", + PURL: "pkg:generic/php-cli@8.2.1", + Locations: locations("php"), + Metadata: metadata("php-cli-binary"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "php-fpm/8.2.1/linux-amd64", + expected: pkg.Package{ + Name: "php-fpm", + Version: "8.2.1", + Type: "binary", + PURL: "pkg:generic/php-fpm@8.2.1", + Locations: locations("php-fpm"), + Metadata: metadata("php-fpm-binary"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "php-apache/8.2.1/linux-amd64", + expected: pkg.Package{ + Name: "libphp", + Version: "8.2.1", + Type: "binary", + PURL: "pkg:generic/php@8.2.1", + Locations: locations("libphp.so"), + Metadata: metadata("php-apache-binary"), + }, + }, + { + // TODO: original binary is different than whats in config.yaml + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "perl/5.12.5/linux-amd64", + expected: pkg.Package{ + Name: "perl", + Version: "5.12.5", + Type: "binary", + PURL: "pkg:generic/perl@5.12.5", + Locations: locations("perl"), + Metadata: metadata("perl-binary"), + }, + }, + { + // TODO: original binary is different than whats in config.yaml + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "perl/5.20.0/linux-amd64", + expected: pkg.Package{ + Name: "perl", + Version: "5.20.0", + Type: "binary", + PURL: "pkg:generic/perl@5.20.0", + Locations: locations("perl"), + Metadata: metadata("perl-binary"), + }, + }, + { + // TODO: original binary is different than whats in config.yaml + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "perl/5.37.8/linux-amd64", + expected: pkg.Package{ + Name: "perl", + Version: "5.37.8", + Type: "binary", + PURL: "pkg:generic/perl@5.37.8", + Locations: locations("perl"), + Metadata: metadata("perl-binary"), + }, + }, { logicalFixture: "haproxy/1.5.14/linux-amd64", expected: pkg.Package{ @@ -280,116 +286,112 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, - // TODO: missing original binaries - //{ - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-2.8.23/linux-amd64", - // expected: pkg.Package{ - // Name: "redis", - // Version: "2.8.23", - // Type: "binary", - // PURL: "pkg:generic/redis@2.8.23", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - - //{ - // name: "positive-redis-4.0.11", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-4.0.11/linux-amd64", - // expected: pkg.Package{ - // Name: "redis", - // Version: "4.0.11", - // Type: "binary", - // PURL: "pkg:generic/redis@4.0.11", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-5.0.0", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-5.0.0/linux-amd64", - // expected: pkg.Package{ - // Name: "redis", - // Version: "5.0.0", - // Type: "binary", - // PURL: "pkg:generic/redis@5.0.0", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-6.0.16", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-6.0.16", - // expected: pkg.Package{ - // Name: "redis", - // Version: "6.0.16", - // Type: "binary", - // PURL: "pkg:generic/redis@6.0.16", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-7.0.0", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.0.0", - // expected: pkg.Package{ - // Name: "redis", - // Version: "7.0.0", - // Type: "binary", - // PURL: "pkg:generic/redis@7.0.0", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-7.0.14", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.0.14", - // expected: pkg.Package{ - // Name: "redis", - // Version: "7.0.14", - // Type: "binary", - // PURL: "pkg:generic/redis@7.0.14", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-7.2.3-amd64", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.2.3-amd64", - // expected: pkg.Package{ - // Name: "redis", - // Version: "7.2.3", - // Type: "binary", - // PURL: "pkg:generic/redis@7.2.3", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - //{ - // name: "positive-redis-7.2.3-arm64", - // logicalFixture: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64", - // expected: pkg.Package{ - // Name: "redis", - // Version: "7.2.3", - // Type: "binary", - // PURL: "pkg:generic/redis@7.2.3", - // Locations: locations("redis-server"), - // Metadata: metadata("redis-binary"), - // }, - //}, - - // TODO: missing original binaries - //{ - // name: "positive-libpython3.7.so", - // logicalFixture: "test-fixtures/classifiers/positive/python-binary-lib-3.7", - // expected: pkg.Package{ - // Name: "python", - // Version: "3.7.4", - // PURL: "pkg:generic/python@3.7.4", - // Locations: locations("libpython3.7.so"), - // Metadata: metadata("python-binary-lib"), - // }, - //}, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/2.8.23/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "2.8.23", + Type: "binary", + PURL: "pkg:generic/redis@2.8.23", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/4.0.11/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "4.0.11", + Type: "binary", + PURL: "pkg:generic/redis@4.0.11", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/5.0.0/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "5.0.0", + Type: "binary", + PURL: "pkg:generic/redis@5.0.0", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/6.0.16/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "6.0.16", + Type: "binary", + PURL: "pkg:generic/redis@6.0.16", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/7.0.0/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "7.0.0", + Type: "binary", + PURL: "pkg:generic/redis@7.0.0", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/7.0.14/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "7.0.14", + Type: "binary", + PURL: "pkg:generic/redis@7.0.14", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/7.2.3/linux-amd64", + expected: pkg.Package{ + Name: "redis", + Version: "7.2.3", + Type: "binary", + PURL: "pkg:generic/redis@7.2.3", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + // note: dynamic (non-snippet) test case + logicalFixture: "redis-server/7.2.3/linux-arm64", + expected: pkg.Package{ + Name: "redis", + Version: "7.2.3", + Type: "binary", + PURL: "pkg:generic/redis@7.2.3", + Locations: locations("redis-server"), + Metadata: metadata("redis-binary"), + }, + }, + { + logicalFixture: "python-shared-lib/3.7.4/linux-amd64", + expected: pkg.Package{ + Name: "python", + Version: "3.7.4", + PURL: "pkg:generic/python@3.7.4", + Locations: locations("libpython3.7m.so.1.0"), + Metadata: metadata("python-binary-lib"), + }, + }, { // note: dynamic (non-snippet) test case @@ -457,51 +459,49 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, }, - // TODO: missing original binary - //{ - // name: "positive-python-3.5-with-incorrect-match", - // logicalFixture: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match", - // expected: pkg.Package{ - // Name: "python", - // Version: "3.5.3", - // PURL: "pkg:generic/python@3.5.3", - // Locations: locations("python3.5"), - // Metadata: metadata("python-binary"), - // }, - //}, - - // TODO: can't seem to get this lined up with the new binary pulled down... the original snippet also has content I can't account for - //{ - // logicalFixture: "python/3.6/linux-amd64", - // expected: pkg.Package{ - // Name: "python", - // Version: "3.6.3", - // PURL: "pkg:generic/python@3.6.3", - // Locations: locations("python3.6"), - // Metadata: metadata("python-binary"), - // }, - //}, - - // TODO: missing original binary - //{ - // name: "positive-python-duplicates", - // logicalFixture: "test-fixtures/classifiers/positive/python-duplicates", - // expected: pkg.Package{ - // Name: "python", - // Version: "3.8.16", - // Type: "binary", - // PURL: "pkg:generic/python@3.8.16", - // Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), - // Metadata: pkg.BinarySignature{ - // Matches: []pkg.ClassifierMatch{ - // match("python-binary", "dir/python3.8"), - // match("python-binary", "python3.8"), - // match("python-binary-lib", "libpython3.8.so"), - // }, - // }, - // }, - //}, - + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "python-with-incorrect-match/3.5.3/linux-amd64", + expected: pkg.Package{ + Name: "python", + Version: "3.5.3", + PURL: "pkg:generic/python@3.5.3", + Locations: locations("python3.5"), + Metadata: metadata("python-binary"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "python/3.6.3/linux-amd64", + expected: pkg.Package{ + Name: "python", + Version: "3.6.3", + PURL: "pkg:generic/python@3.6.3", + Locations: locations("python3.6"), + Metadata: metadata("python-binary"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "python-duplicates/3.8.16/linux-amd64", + expected: pkg.Package{ + Name: "python", + Version: "3.8.16", + Type: "binary", + PURL: "pkg:generic/python@3.8.16", + Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), + Metadata: pkg.BinarySignature{ + Matches: []pkg.ClassifierMatch{ + match("python-binary", "dir/python3.8"), + match("python-binary", "python3.8"), + match("python-binary-lib", "libpython3.8.so"), + }, + }, + }, + }, { logicalFixture: "go/1.21.3/linux-amd64", expected: pkg.Package{ @@ -522,18 +522,18 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("nodejs-binary"), }, }, - // TODO: captured outside of the original binary - //{ - // name: "positive-go-hint", - // logicalFixture: "test-fixtures/classifiers/positive/go-hint-1.15", - // expected: pkg.Package{ - // Name: "go", - // Version: "1.15", - // PURL: "pkg:generic/go@1.15", - // Locations: locations("VERSION"), - // Metadata: metadata("go-binary-hint"), - // }, - //}, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "go-version-hint/1.15/any", + expected: pkg.Package{ + Name: "go", + Version: "1.15", + PURL: "pkg:generic/go@1.15", + Locations: locations("VERSION"), + Metadata: metadata("go-binary-hint"), + }, + }, { // note: this is testing BUSYBOX which is typically through a link to "[" (in this case a symlink but in // practice this is often a hard link). @@ -545,56 +545,55 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("busybox-binary", "[", "busybox"), }, }, - // TODO: need to get the original binaries - //{ - // logicalFixture: "openjdk/1.8.0", - // expected: pkg.Package{ - // Name: "java", - // Version: "1.8.0_352-b08", - // Type: "binary", - // PURL: "pkg:generic/java@1.8.0_352-b08", - // Locations: locations("java"), - // Metadata: metadata("java-binary-openjdk", "java"), - // }, - //}, - //{ - // name: "positive-java-openjdk-lts", - // logicalFixture: "test-fixtures/classifiers/positive/openjdk-lts-11.0.17", - // expected: pkg.Package{ - // Name: "java", - // Version: "11.0.17+8-LTS", - // Type: "binary", - // PURL: "pkg:generic/java@11.0.17+8-LTS", - // Locations: locations("java"), - // Metadata: metadata("java-binary-openjdk", "java"), - // }, - //}, - //{ - // name: "positive-java-oracle", - // logicalFixture: "test-fixtures/classifiers/positive/oracle-java-19.0.1", - // expected: pkg.Package{ - // Name: "java", - // Version: "19.0.1+10-21", - // Type: "binary", - // PURL: "pkg:generic/java@19.0.1+10-21", - // Locations: locations("java"), - // Metadata: metadata("java-binary-oracle", "java"), - // }, - //}, - //{ - // name: "positive-java-oracle-macos", - // logicalFixture: "test-fixtures/classifiers/positive/oracle-macos-java-19.0.1", - // expected: pkg.Package{ - // Name: "java", - // Version: "19.0.1+10-21", - // Type: "binary", - // PURL: "pkg:generic/java@19.0.1+10-21", - // Locations: locations("java"), - // Metadata: metadata("java-binary-oracle", "java"), - // }, - //}, - { - name: "positive-java-ibm", + { + logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64", + expected: pkg.Package{ + Name: "java", + Version: "1.8.0_352-b08", + Type: "binary", + PURL: "pkg:generic/java@1.8.0_352-b08", + Locations: locations("java"), + Metadata: metadata("java-binary-openjdk", "java"), + }, + }, + { + logicalFixture: "java-jre-openjdk/11.0.17/linux-amd64", + expected: pkg.Package{ + Name: "java", + Version: "11.0.17+8-LTS", + Type: "binary", + PURL: "pkg:generic/java@11.0.17+8-LTS", + Locations: locations("java"), + Metadata: metadata("java-binary-openjdk", "java"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "java-jre-oracle/19.0.1/linux-amd64", + expected: pkg.Package{ + Name: "java", + Version: "19.0.1+10-21", + Type: "binary", + PURL: "pkg:generic/java@19.0.1+10-21", + Locations: locations("java"), + Metadata: metadata("java-binary-oracle", "java"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "java-jre-oracle/19.0.1/darwin", + expected: pkg.Package{ + Name: "java", + Version: "19.0.1+10-21", + Type: "binary", + PURL: "pkg:generic/java@19.0.1+10-21", + Locations: locations("java"), + Metadata: metadata("java-binary-oracle", "java"), + }, + }, + { logicalFixture: "java-jre-ibm/1.8.0_391/linux-amd64", expected: pkg.Package{ Name: "java", @@ -605,43 +604,65 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("java-binary-ibm", "java"), }, }, - // TODO: need to get the original binaries - //{ - // name: "positive-rust-1.50.0-macos", - // logicalFixture: "test-fixtures/classifiers/positive/rust-1.50.0", - // expected: pkg.Package{ - // Name: "rust", - // Version: "1.50.0", - // Type: "binary", - // PURL: "pkg:generic/rust@1.50.0", - // Locations: locations("lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib"), - // Metadata: metadata("rust-standard-library-macos"), - // }, - //}, - //{ - // name: "positive-rust-1.67.1-macos", - // logicalFixture: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin", - // expected: pkg.Package{ - // Name: "rust", - // Version: "1.67.1", - // Type: "binary", - // PURL: "pkg:generic/rust@1.67.1", - // Locations: locations("lib/libstd-16f2b65e77054c42.dylib"), - // Metadata: metadata("rust-standard-library-macos"), - // }, - //}, - //{ - // name: "positive-rust-1.67.1-linux", - // logicalFixture: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl", - // expected: pkg.Package{ - // Name: "rust", - // Version: "1.67.1", - // Type: "binary", - // PURL: "pkg:generic/rust@1.67.1", - // Locations: locations("lib/libstd-86aefecbddda356d.so"), - // Metadata: metadata("rust-standard-library-linux"), - // }, - //}, + { + logicalFixture: "rust-libstd/1.50.0/linux-amd64", + expected: pkg.Package{ + Name: "rust", + Version: "1.50.0", + Type: "binary", + PURL: "pkg:generic/rust@1.50.0", + Locations: locations("libstd-6f77337c1826707d.so"), + Metadata: metadata("rust-standard-library-linux"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "rust-libstd/1.50.0/darwin", + expected: pkg.Package{ + Name: "rust", + Version: "1.50.0", + Type: "binary", + PURL: "pkg:generic/rust@1.50.0", + Locations: locations("libstd-f6f9eec1635e636a.dylib"), + Metadata: metadata("rust-standard-library-macos"), + }, + }, + { + // TODO: find original binary... + // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo + logicalFixture: "rust-libstd/1.67.1/darwin", + expected: pkg.Package{ + Name: "rust", + Version: "1.67.1", + Type: "binary", + PURL: "pkg:generic/rust@1.67.1", + Locations: locations("libstd-16f2b65e77054c42.dylib"), + Metadata: metadata("rust-standard-library-macos"), + }, + }, + { + logicalFixture: "rust-libstd-musl/1.67.1/linux-amd64", + expected: pkg.Package{ + Name: "rust", + Version: "1.67.1", + Type: "binary", + PURL: "pkg:generic/rust@1.67.1", + Locations: locations("libstd-86aefecbddda356d.so"), + Metadata: metadata("rust-standard-library-linux"), + }, + }, + { + logicalFixture: "rust-libstd/1.67.1/linux-amd64", + expected: pkg.Package{ + Name: "rust", + Version: "1.67.1", + Type: "binary", + PURL: "pkg:generic/rust@1.67.1", + Locations: locations("libstd-c6192dd4c4d410ac.so"), + Metadata: metadata("rust-standard-library-linux"), + }, + }, { // note: dynamic (non-snippet) test case @@ -695,18 +716,17 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, }, - //{ - // name: "positive-ruby-1.9.3p551", - // logicalFixture: "test-fixtures/classifiers/positive/ruby-1.9.3p551/linux-amd64", - // expected: pkg.Package{ - // Name: "ruby", - // Version: "1.9.3p551", - // Type: "binary", - // PURL: "pkg:generic/ruby@1.9.3p551", - // Locations: locations("ruby"), - // Metadata: metadata("ruby-binary"), - // }, - //}, + { + logicalFixture: "ruby/1.9.3p551/linux-amd64", + expected: pkg.Package{ + Name: "ruby", + Version: "1.9.3p551", + Type: "binary", + PURL: "pkg:generic/ruby@1.9.3p551", + Locations: locations("ruby"), + Metadata: metadata("ruby-binary"), + }, + }, { logicalFixture: "consul/1.15.2/linux-amd64", expected: pkg.Package{ diff --git a/syft/pkg/cataloger/binary/test-fixtures/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/.gitignore index 9a442ff1e01..18a7c41bd44 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/.gitignore +++ b/syft/pkg/cataloger/binary/test-fixtures/.gitignore @@ -1,2 +1,3 @@ classifiers/dynamic -classifiers/bin \ No newline at end of file +classifiers/bin +cache.fingerprint \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/Makefile b/syft/pkg/cataloger/binary/test-fixtures/Makefile index 2eaf95f4428..5d752c2be3b 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/Makefile +++ b/syft/pkg/cataloger/binary/test-fixtures/Makefile @@ -1,4 +1,4 @@ -.PHONY: default list download download-all fingerprint +.PHONY: default list download download-all cache.fingerprint .DEFAULT_GOAL := default @@ -13,9 +13,11 @@ download: ## download only binaries that are not covered by a snippet download-all: ## download all managed binaries go run ./manager download -fingerprint: ## prints the sha256sum of the any input to the download command (to determine if there is a cache miss) - @cat ./config.yaml | sha256sum | awk '{print $$1}' +cache.fingerprint: ## prints the sha256sum of the any input to the download command (to determine if there is a cache miss) + @cat ./config.yaml | sha256sum | awk '{print $$1}' | tee cache.fingerprint +clean: ## clean up all downloaded binaries + rm -rf ./classifiers/bin ## Halp! ################################# diff --git a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh index c154b804a5f..2767682d326 100755 --- a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh +++ b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh @@ -2,7 +2,7 @@ # Default values for length and prefix length LENGTH=100 -PREFIX_LENGTH=10 +PREFIX_LENGTH=20 SEARCH_FOR='' GROUP_NAME='' diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore deleted file mode 100644 index 5acc24bd654..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!*.so -!VERSION \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.1.16-2c336c63e26881d2f02f34379024e7c314bce572c08cbaa319bacbbec29f93ed-1210190-100/bash deleted file mode 100644 index 0fb07cbeac7d2bdcf94f5125c3a5796a3aeb3670..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmYLG!HU~35Y4$?G2lxAwqr@QBL@mg*t8VFUa~EeQm7=4)@ohH$Vy3W`zigvLiYzk zI!@A7FdFejdYXAJiXwQz4!sIkgFeDwyz2vDA+hcd_8ya=bA68?9U4#p9`^|MXn<9~ z*s>^>qJs7I5qQQ0L{aoA0_$4r)OTFMb!jy;1P_hkbpHkda3e%%N`btTlGWU1+=^T* zs*LBjFoNaUV6osPlSV5gSf!QG8V#>(C9uBz1g^8Y8|!-W2Fg6qF2H8Hg5DwE0nI)r z-C_&}Stey3!w>X>+jYQTy8XEQFpHr+Qp3)uwmm}Yx^Gy=#2e7}9%+Yp>vWmV+d84@ z{*YXm(qWhK7*>1N*6;2=(SYppYavuo6nQ3ci<&DdBucX=rX$_3%%=k?Z<^T>9=7>m^L5QfErap_cGeCU#@QKX8LKj? zkn~TX`L8m|2ql;Qz5wky<;m!)s~5`*jX1fiyJ<9AkzM~JDpHoRa>{1lu|;ZV?07PH demR|Aex1%=DBn>4#n0cT^E1T-zvc0KjX$px&dvY; diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ deleted file mode 100644 index 7829d71b941..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ +++ /dev/null @@ -1,3 +0,0 @@ -# note: this SHOULD match as busybox 3.33.3 - -noise!BusyBox v3.33.3!noise \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox deleted file mode 120000 index c3e3150b864..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox +++ /dev/null @@ -1 +0,0 @@ -./[ \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/erlexec-25.3.2.7-2224b3107551c409fb8da6d85eaa1ed730d088db438924c97d6e25851381dd28-91724-100/erlexec deleted file mode 100644 index 9c3924d77fff8a201c2a845477f39eab89b73f71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmYLHU2EJh5WMI7iUq$UU`v)Pe;uUIkhB!;`p`=XrIa8`>pP`7+emVI_wOqw*H#EU z%r3J#GfI*K_Bf!E0X1}I@XDDfL@Z>zIfdC_<_9xP7>GZB4DdK3+@k{40%L1duSE_I zyGP(TFCa;hs|0#uagaw`!`ne;@q{W?{6X@v`ilc_&P7+Sye>mt{A)96kVP0NL9Yh58Po01g`)0#JzVPJ-Sys$oOK0qb1&IH)(?qD(q%kVkK&SDDh zzidHDrw)}!;TJk@#sL^y@4oH6-lU+8RCNtj(wF zLPY#XSPFNuvHH{f_b8J6xUEDkm}pyBl||H4V=pCEyu-4ngl-C1SG?q{OrakgeEj@G zHwz~eq$YU^zfCxR#%|s}FXB=d#sEIVa3<7Ib|*bn;@ab)b7S99CpS2#q%y_PjJ-;8zMWC_8JU7yqK0e*n07!ruS@ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.21.3-6a05bd57efff25a9d682b8e25ce572ee7ff723f4f5bcbba6429012121e247365-7879390-100/go deleted file mode 100644 index f0eff60bb31ae974adb5c2e58e7aed59d4d91647..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 444 zcmYLFJx{|h5KR{b2ADu%J1GkYrHY-{`H&bY!B{c0JF?SrVyPq{aio@sUjZ9G0R9IP z0|Of?|A7^ow&E=N`JVmG=jZ!AlvraY3@$*fp)We!8q1kL>6IBe4EkEPEt+v(fiN&1 z5H7I*k^!MP$#R;))oczlq!IYOzwd+6ORU8$=CFyfv1DHf%J_}~;38yl!4gzTQ6{nl zPpM3ivI1E`5fh~n8mU+@Sx6~(OtX+sJCMdn#M$;8(6wr|Ri~n;f@|P$AhiKpVKEpX zmk6tr-|*Nfr09{yr#*_UsLpbj&f>M5_pU!9Is=VTJsMXpNs}zfLVIFAQ=00(a}rwC z`nFSm)V1iWbsL}lOz<8&cpZ5*%7%<78AopKq7}6+YQ4N5n|!x^o?p(L;PL4D&Edz> Z$>8wWdwF+vCVO{ZE}tBaejFYi`~nLxa%TVl diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION deleted file mode 100644 index 5bedbed9f95..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION +++ /dev/null @@ -1 +0,0 @@ -go1.15-beta2 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14-5ff6ed7c0149382701391f268e78793a623d6b8fd2b9b65fe89c08b6041fd4d4-527350-100/haproxy deleted file mode 100644 index f2f6760e407ba66ff72ac8a57996fb2cd6b28c02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 638 zcmYL{ZHv?}6ovP*zv4m>cA@PgX_B^sg04Hhz$n6uiik+k+)SgjEoth`{Cm@#SvDU6 zxhJ{FIZu)#fdlsFRX_v!1XJVP7-B7C)*Zv_F`Iffju`T(1r=aFBivyFvq1FZ7^y33n69-H7OT1;m6n84Yq;T;%fR(^xOClY--6_sb^*4#TfieWb5Od&6poe5 zBv0WV`pFGFP`KWG-+jGF!JOiIU8B101fA>uph;uTfPVBCXIOPkOTOw%7S;Kfojc{z zkh2u*;Njh;FRSe$0n261WD2*l>&%C{AJH=X`H~wgNLf%M#@2#JWh-L^CzWhy)6|Nv zXjjxUg?krTFsSFluf(`~f2B~88E|K4o^m z^D=KT?@f{fFR5ko$;paMM32)Af})tm6Ok0LB!|uv(xj8pav7tSYSJv?4?r zTGxtNQyZ;lA!Q*7+6ak8W33xgvC@|3X5ji}IJ4eu-T^N&?E-AJH!wN`+@qO;(jBI- zmptQT3V+d0Zs>u+)%M%=%XJF&5dZ9q>be7TuK$fTjl2Q<=rNwK>YV1~s@yhl_+(| zYDsF*=oFq@Xu+bMcR!cGQs{>OQ;5clXyeFQO~}iu36(eP^%_2G%X0JU;V5uWKETj4 z6NW{QC>$3hi$&3rU1*=Br-WQayCUzY0<`Ouj|*^e`)K8huCw{Y67Sz+pB4|Fb<1VW uSahA%D@sr8Nfcvnx!O$d+Te^sK(uw!(cJAorl2(VS3bIWF-d_`T2m-RrSRhZf(vs zUvDoD1bgW`bjA$m4}>tv1t(dk1W9(j{!cw6t0(kB{C*EHSzu=x2bJ@)@T`Nv;#{O9@XA5VVqFvsxl7a)_dL;wH) diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy deleted file mode 100644 index da21a03ddc887b7b5cf87c55c037959fbaee7cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5474 zcmeH}*-{fx5QYcu^hPaxfTJjGkW3O5#ckNb9+m{r5(msg1CfzLv#?mjgIT_fx4eQs znNxixEtxUPNI~T*iqq5Q^xyyKNu_e~>*vX(`}=)-eEBXF^o_c@g6_~R{j0u08ga*m zyFNVh;gJuoeR#ll!Z>7fOI}G%FM1Cc;2n4met?aQqoWt*fu>6$lBi@;@=;<*vJzWT zkbIK}a3A!65D0@9=m!H}5DbB*;2C%cM!*NK1=1h`K7r352flz^@D=32cQ8CWIyyEs zK0YxqIXN{Ik55m}%*@Wt&CSm*EG#Z25=%?V%PT9ZtI1?4wYIjtzP-J(v$t0)mP+Mv zrLw=@^SJlb5;ELdp~)uXYn9t}tOtDhP6 zsMVC=M0_NXx62ih&Dgn|T`Fk!NL~+B6>1_pQ1%9oPK%tJpva)eqzJ*8ajW$Zy-A+j z9d7-dRlT&^&aS_|dcFursc>?BwB#5`iBNHsBW z*2#GkxzAWqA>*tb(%72!6tMe?GiT+LO}Y8m zG9mYCGYXpb}6KE2$^t&U0Bo zn+8RVHPEO|MS^k$MJ7cE>c!Gt^`~^_UuOcH33MiK{u79pJyjD|q!s52th@SzICl28 J7IxCt{s2GBxzGRr diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd deleted file mode 100644 index d121fdbd61dfcb71d19b3228273e9c72a934fa95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 652 zcmYjP+lt#T5cPAuVu)XM!EvmMF9}It)7>r$A+%&$x|C96X%vfkZ6nKV(yy=FG;Q@D zh8dlsnR6x#LpWlCRyb5po}ri4bk678Oq-J%EYAAIbRD|5uRu6>9uV$P0$BiIOO`Iv z93BqOKoiO!48utX+SJ&HS6ss7RL*tE=le>~G`nU27!sn(GShLMNi6 zJTG}c^PEY=2&KHhvdH`cni6Clqesf|D=>|2|FAyn-a^J_rE#!3Y@ss_=2JB$8?d2Py!&ucaF)!{U| zFvWcv(+IYMsnyf{XFnkMvMz`$P!W_#q6*0qmZV~lGOATx=u{~wGo5NKBG9dc$Nh)- zZlnYj(>RIXhjA4sEQjOQaj*!Q)JJcZ1pT!pN}vP3RM1||j&gTYYGf&mDCl%9cZT7FS#Zem`%k%5s> zyn$srkTf)nH#X2sGB5zCVqjpDX0|o~(yRTs0?c=3gsDB2Jbff%lQ>K iC#i9g(Y4Ss&@(pC2QicLb93|a^pkTk nQ}asV3vx=+GxPM4lNt0&i;MJg@{<#DAbRw3GL!Tn(hQye3?U$f diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-1.25.1/nginx b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-1.25.1/nginx deleted file mode 100644 index 6311b0515cedb3e623de1e56834b671143899dab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 zcmd6lu~Ne@42GHe6dK_!O|RUsWMN`$7mnQ|isL)Sa&4vb@sV88OUl5C7u%A)U-ExG zMs^f(vjNz?R@(vQ)c_AJ!+<_&?mWkJZy*o!1!qiIU@)Nryg1gnk3Z^K3ca&aLZEAB zX@lX0^BQN&3^QVM(p9tfzp5+frZIx!6?R%DOPZr99NK#A_m<(nNHQB6k_i#H#MFE z(eMC|9rkAO+`!>@czaR;p?4mG8BrMR&v1Q#CtFerNJ4p6vb7uw-u}kdZmar2LX*2- zUAWYGEE*O%m#}4b7Qe0|SWlng$s$e@1NC=4s`zH}@Q+t{>io|eDA(P-etxO<%elV+ D&cM__ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2/nginx b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2/nginx deleted file mode 100644 index 79aa8cda4c4f8e2dcfd51ec129d9125d29e93cda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1425 zcmd5)O>fjN5VbgR;s(c&(Mqk_-L@-E6%ufPOAkmuFV!lz@g%j#+V0p{b}4@i@ss!q zOybQ4-L~R{h$7F-oA<`gPK0QmUq64O_o0dziT!sg)3KBI=ij=?vd<1ucD#+u-Da=1 z>?hlJJg8qL&20z&c(14O!`K1u{_Oi05RBaUcBFB$!k32K=^e)Re{BcYB6jX_)kk}W~ bS68d^`0;uH7d$`kucoRd_uvT&vtIlL5UPb) diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/node-19.2.1/node b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/node-19.2.1/node deleted file mode 100644 index 9e7608b560f..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/node-19.2.1/node +++ /dev/null @@ -1,2 +0,0 @@ -# this should match node 19.2.1 -node.js/v19.2.1 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-1.8.0/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/openjdk-1.8.0/java deleted file mode 100644 index 1193a1ef5281dd536e34f2461f52a3c48ee3b674..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39 rcmZQzU}Ruo$S+9E%Sy>+$Vx0rWH8jT0AW3YcwBLljloIPBF;$2*w zLfsks{6k!=6aq4nvr|(PN(&TJi>-i|A-^CsFDoUR0c0wJft~?_p`o6Eo}sz6g)Yzz S23;@R++vGd21aRSYc2rv(;^N4 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so deleted file mode 100644 index 8d3b0c69870..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-apache-8.2.1/libphp.so +++ /dev/null @@ -1,5 +0,0 @@ -[null] Script: '%s' -%s,%s -X-Powered-By: PHP/8.2.1 -index pointer -PHP_VERSION diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php deleted file mode 100644 index 8d3b0c69870..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-cli-8.2.1/php +++ /dev/null @@ -1,5 +0,0 @@ -[null] Script: '%s' -%s,%s -X-Powered-By: PHP/8.2.1 -index pointer -PHP_VERSION diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm deleted file mode 100644 index 8d3b0c69870..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/php-fpm-8.2.1/php-fpm +++ /dev/null @@ -1,5 +0,0 @@ -[null] Script: '%s' -%s,%s -X-Powered-By: PHP/8.2.1 -index pointer -PHP_VERSION diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15.1/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15.1/postgres deleted file mode 100644 index 149da9050f13a0ddbb309a5ab4285b06ccaea3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZPw$S*EQFG>v#^ieQ0)iVSDE3O2( diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-15beta4/postgres deleted file mode 100644 index 50f6b3d2dd49b7a9273e0918214e3c2921a32acd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19 acmZPw$S*EQFG>v#^ieQ0O-e0EGywoU0tPDp diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.5alpha1/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.5alpha1/postgres deleted file mode 100644 index 1188fb403f4..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.5alpha1/postgres +++ /dev/null @@ -1,5 +0,0 @@ -byteaout -byteain -?PostgreSQL 9.5alpha1 on x86_64-unknown-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit -/tmp/buildd/postgresql-9.5-9.5~alpha1/build/../src/backend/utils/adt/windowfuncs.c -argument of ntile must be greater than zero diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.6.24/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/postgresql-9.6.24/postgres deleted file mode 100644 index cc29b7ae893cf0e7a87dca37dc126988c11a79ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18 ZcmZPw$S*EQFG>v#^iiv@vt$T}4Ds;y^YnN1bM|-f^mAu0)-%&HX0RzJN=?hGu+z`UOwxy_ Rj?jx>2mtF-P%Y+S007lR6v+Sp diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-binary-lib-3.7/libpython3.7.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-binary-lib-3.7/libpython3.7.so deleted file mode 100644 index b97a0767bf748e50557fa98193674ebece7b60c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmXS7D5xyS$j>v@vt$T}4Ds;y^YnN1bM|-f^mAu0)-%^LVX!GEN=?hGu+z`UOwxy_ Rj?jx>2mtF-P%Y+S007mG6w3er diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-duplicates/libpython3.8.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/python-duplicates/libpython3.8.so deleted file mode 100644 index a1b0d2429480c7504bf546011a15435fd900d33c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmY$$P0Y+w$jmEAEh;EV1(FQOiFxWJ3b~2dsS0VCMa3lwB^gDji75;|&hd^uJ`Bcs z7J7ze48 RRE2=Z5D$O9aK|7&E&$|XC_exI diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-2.8.23/redis-server deleted file mode 100644 index f56e59bbf678a2663f08946ae4574cf01208c0c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7025 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 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-5.0.0/redis-server deleted file mode 100644 index 124d42a598f5d504cbc6f1dda3662cc2df497e55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7061 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 GBhK$D zS%qdlbh|KU$8S-9{=|-rOx-_r4op8a8y<)cEl>SIX$=r(Bss$a**8?8n77Wkx zZm~lxM2&LDe}Dfv{QBYRAf6CwjGqNz5V9z0pGWhIo@cZb{e9X#Yq$S8uc2W`2K~XO zVR8kHFY)vx88^vQJe$NHhZFac^lxHvJsgdo%ZpB-wCxeV@d56p@dUc1VOpk3R(q${ z!`qv9pKCOn&fs1ZmMdu>2!aQ4m)!P|OsW~WmM@tqZ4d7MnG9#~OMf(aAd~oZ^aUE@ zngtQ*bRsfPQnK7hhZRTuGBuWI5X#tq^z)n* zmQl#}D67CT`-o>c9wn;P!zxj?d*144Hh;{7$`_A?GNf`8V)IyCVYqMtW382jmF5;D z0;b%l#H(cpW(*xEUs8B;0mi_nn0Ae5V%!JHO)Uxe+*ZU^Jek05J5x9*ZDN&z zj3Yt{E*WU{y=2B>>9Nobnd+x}r>I6Qvr;VZyrr~YtVVebhcvy(%K5$f&)l&d9PRyw z_k>HluAIUFk(4qmT`l*yT#{ueEO#z!74)zQ^`3iJy=9%WitIXDCp~oC?Rr|D3+$#~ zqrW%tRh+oMp|6XiH{SKeP`h61<~0P j@v8;R3vY|BeZ5|H$Q$>B6HNcvJ@bZ;*qhuF^!VsMCHZ^~ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.0.14/redis-server deleted file mode 100644 index 68dae86a938b5803d1c6dd158d7a19d1e170570c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 890 zcmbtSO-~y!5H*Dhf!;W9=)qOBRU&1hO~gltniWx%^b+92Ioh?SVAj|x+shIz{0{nS z`ZId#mHdijoFziyNGv^Bn)lwkvBr<%>EzSK23eg=5;wMU19v7VB`7N%U_gUj(Z*^r zmpb7zI4l~q{6XYWSoaHDGjG%HcZn>(-)LN3SZI0WJ6#5Iq_bL;$y&AhXHlK5fxR`n zKKBb^>jOAI&pzU84K&yA`rPNIMfL9k=%d%~X9rQQ^lw#F|3}g5A%T?l$Q^g0PWK=! zgyflUHc>p)_hI+wq~DLC!{gpc9}xwv)Fu<^s>28__{v?=ct|5Mx|oFi_aGB8DL^^$ zqfnXBrHV`mn~4eT2e*g?RF z4&%b24%1l}8~8m-rDoYI*On{`xt!0^xdTgwe!#IY+R)Mnha*yKSx@XEeh|f~9%^~LOjZk* zwZ>m_RXo5^)5v0>rj$XC?Ca70IwbTZgIJvlwMUD25Gj4NGyFYqnUA0L`IU!-1eaK$ zedu4wT&lU^67mbp-HSnKzKu<{P2+Vc?Qff9SS#PQ=uMv7?h~_+=$~pI_h4BfD Cq3A~d diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/redis-server-7.2.3-arm64/redis-server deleted file mode 100644 index 15ad151acb127f38b9e7cc55e09a8cf56d5981e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 816 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 W;f^5K8V^R`_%GK}1(XZ#6aE(%O>$uX diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin/lib/libstd-16f2b65e77054c42.dylib b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin/lib/libstd-16f2b65e77054c42.dylib deleted file mode 100644 index d5949c118f02fee7c802bd96390c0d20913063f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28955 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 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-1.7.34/traefik b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-1.7.34/traefik deleted file mode 100644 index 12602288d828264925ff25667ed4e1df060a2436..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9 QcmZQ5Gt@KJGd5uW00{5_q5uE@ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-2.9.6/traefik b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/traefik-2.9.6/traefik deleted file mode 100644 index f5b1dc0ccff056e281cb387af90705dc96040677..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7 OcmZQD(zDbvV*mgH^8o$; diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/haproxy/2.7.3/linux-amd64/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/haproxy/2.7.3/linux-amd64/haproxy index 0f150f2b31260f13d90b7518f4baeadfe64afd66..c3741bd552363335991e45e87c7442b07776dfad 100644 GIT binary patch delta 74 zcmV~$!4ZHU3$eq9Fi$ Wkh!~NP?v1XU^q#&o@8kC{nigqLl#c} diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/1.8.0_352-b08/linux-amd64/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/1.8.0_352-b08/linux-amd64/java new file mode 100644 index 0000000000000000000000000000000000000000..4a09e1605e99d8d964e828e8e86de393b339d455 GIT binary patch literal 325 zcmXxfJ4ysW5C-60#iV01lZJ_1s;VFMjH#iK9zgn0?e03x4D6t2Z0J!8J;Gi~#$M2Z zpD*~Ks2ajEcWM`2f*ecP^V}&SK-u%~IBzE_WZvLLxz;w;=IWz7t7H_v^gbf!8xK3 z(J%&XGkPxUt&JunFla&h;IouX)2R41v-&$z&7&$~-;b}VR#KdZapGZ@pTzM?fop%v x8XmNz%LrbG)y3&G2qDgDK7N+vw)$P2tB2}m)hx^MRlTVms^>MwS-szh)i0q6T^axY literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/11.0.17/linux-amd64/java b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/java-jre-openjdk/11.0.17/linux-amd64/java new file mode 100644 index 0000000000000000000000000000000000000000..74e31cdf139bf8ea34798713e624bf2820482e20 GIT binary patch literal 325 zcmXxfOG*SW5C-6gxXId`o50{oW=JKeq|@F&TnKsq(@Djdv8Q{W8y~muNFKvuxL1rB z(1PMy{8UjjgdMeF8=e9mbDn5!g+`-zPs78!ZUsomr(v_-(|o%Q3T-Q-iR5C?9y+ql zk&S3{LTtd0H>631uF=6enKCA?u|WW-F@g0l7|HXdr~hSE=Oh&XrQqtU=`;a#5kqHV z6Ac;(%6nyER)Hcyz%Bwx3&w#1g`6)I3(>vKB>v76^C044&sT4c0CgTln%H!QVV}=-=bGY9Eqb{4~!PU&o;obqmH+Okh(8%XnQeQ^-J30I>OU#u)vn&I3j|hJ>hyiSHF5DoU wXx@<1Y1^@s6 literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/ruby/1.9.3p551/linux-amd64/ruby b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/ruby/1.9.3p551/linux-amd64/ruby new file mode 100644 index 0000000000000000000000000000000000000000..a91aecbd6cc2cd18f429cf4352ef1825b302eb8f GIT binary patch literal 328 zcmYjM%TB{U5Q{kOSB%sHa7Z7lB!Li{dBm%e&z!*A6ZE6X9UNyCw^=b0FXSD}J%pKHVq#*l z?#_;teD!|?KQ)AVY(;qpNZlPDiPl(J0meJ8l;=GTw`JK1B_&@E-D-v9W*IctR*-d& zJtosQAWY-|Vsh9dV^x&WHAXsz050rhPJ{?v$K)mC+$hPZ>+!z~YD(HFB7-sR>V0a^ zXtV*Xg=|S{tW%?P&`GIiYVVxN5LIoown&Qes;WeMDk$bRMHvN+eLp^m-H9`H$_}$p v3%4*edp$qom)Yl;mowSt=K5^6KY#xoP3q(4c;egF$wYj=*z;-%jBU4nnYB@l literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/traefik/2.9.6/linux-amd64/traefik b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/traefik/2.9.6/linux-amd64/traefik new file mode 100644 index 0000000000000000000000000000000000000000..d5fcd2c227c5a1dbed11c5846e8f202a6e7048b0 GIT binary patch literal 332 zcmXwzO-{rx3`V;{%8CUCh%^$*QS6EHQ+f%+mQCC^GZm$+m`aEp++er?E6z!!3En(E z*|NVWy-KUsw 0 { for _, arg := range args { binaryFromImageCfg := appConfig.GetBinaryFromImage(arg, "") diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go index 82b12716760..6757794b3f6 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go @@ -2,15 +2,16 @@ package commands import ( "fmt" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" - "strings" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" ) func List(appConfig config.Application) *cobra.Command { - var showPaths bool cmd := &cobra.Command{ @@ -18,7 +19,6 @@ func List(appConfig config.Application) *cobra.Command { Short: "list managed binaries and managed/unmanaged snippets", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(appConfig, showPaths) }, } @@ -29,7 +29,6 @@ func List(appConfig config.Application) *cobra.Command { } func runList(appConfig config.Application, showPaths bool) error { - material, err := internal.ListAllEntries(appConfig) if err != nil { return err @@ -42,6 +41,8 @@ func runList(appConfig config.Application, showPaths bool) error { return nil } +const yes = "yes" + func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.EntryInfo, showPaths bool) string { t := table.NewWriter() t.SetStyle(table.StyleLight) @@ -54,7 +55,7 @@ func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.Ent isConfigured := "" if info.IsConfigured { - isConfigured = "yes" + isConfigured = yes } bin := "" @@ -64,11 +65,11 @@ func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.Ent snippet = info.SnippetPath } else { if info.BinaryPath != "" { - bin = "yes" + bin = yes } if info.SnippetPath != "" { - snippet = "yes" + snippet = yes } } diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go index b240868e99c..4c4aa914833 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/root.go @@ -1,8 +1,9 @@ package commands import ( - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" "github.com/spf13/cobra" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" ) func Root(_ config.Application) *cobra.Command { diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go index a0880455cf4..400f7d2ea45 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/write_snippet.go @@ -5,14 +5,16 @@ import ( "debug/macho" "debug/pe" "fmt" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" "io" "os" "path/filepath" "strings" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" ) func WriteSnippet(appConfig config.Application) *cobra.Command { @@ -102,7 +104,7 @@ func runWriteSnippet(binaryPath string, offset, length int, snippetPath string) return fmt.Errorf("unable to marshal metadata: %w", err) } - splitter := []byte(fmt.Sprintf("\n### byte snippet to follow ###\n")) + splitter := []byte("\n### byte snippet to follow ###\n") var finalBuf []byte finalBuf = append(finalBuf, metadataBytes...) @@ -113,7 +115,7 @@ func runWriteSnippet(binaryPath string, offset, length int, snippetPath string) return fmt.Errorf("unable to create destination directory: %w", err) } - if err := os.WriteFile(snippetPath, finalBuf, 0644); err != nil { + if err := os.WriteFile(snippetPath, finalBuf, 0600); err != nil { return fmt.Errorf("unable to write snippet: %w", err) } @@ -195,6 +197,11 @@ func getPlatform(binaryPath string) (string, error) { return "", fmt.Errorf("unable to determine platform for %q", binaryPath) } +const ( + amd64 = "amd64" + arm64 = "arm64" +) + func getPlatformElf(f *os.File) string { elfFile, err := elf.NewFile(f) if err != nil { @@ -204,9 +211,9 @@ func getPlatformElf(f *os.File) string { var arch string switch elfFile.Machine { case elf.EM_X86_64: - arch = "amd64" + arch = amd64 case elf.EM_AARCH64: - arch = "arm64" + arch = arm64 // TODO... default: arch = fmt.Sprintf("unknown-%x", elfFile.Machine) @@ -224,9 +231,9 @@ func getPlatformMac(f *os.File) string { var arch string switch machoFile.Cpu { case macho.CpuAmd64: - arch = "amd64" + arch = amd64 case macho.CpuArm64: - arch = "arm64" + arch = arm64 // TODO... default: arch = fmt.Sprintf("unknown-%x", machoFile.Cpu) @@ -244,9 +251,9 @@ func getPlatformWindows(f *os.File) string { var arch string switch peFile.Machine { case pe.IMAGE_FILE_MACHINE_AMD64: - arch = "amd64" + arch = amd64 case pe.IMAGE_FILE_MACHINE_ARM64: - arch = "arm64" + arch = arm64 // TODO... default: arch = fmt.Sprintf("unknown-%x", peFile.Machine) diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go index 54473351a79..d18663629d9 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application.go @@ -2,12 +2,13 @@ package config import ( "fmt" - "github.com/hashicorp/go-multierror" - "github.com/scylladb/go-set/strset" - "gopkg.in/yaml.v3" "os" "path/filepath" "strings" + + "github.com/hashicorp/go-multierror" + "github.com/scylladb/go-set/strset" + "gopkg.in/yaml.v3" ) const Path = "config.yaml" @@ -96,7 +97,6 @@ func (c Application) Validate() error { if len(entry.PathsInImage) == 0 { err = multierror.Append(err, fmt.Errorf("missing paths for entry %d (%s)", i+1, key)) } - } return err } diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go index ea873707d27..f26ac3ae40a 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image.go @@ -3,9 +3,10 @@ package config import ( "crypto/sha256" "fmt" - "gopkg.in/yaml.v3" "path/filepath" "strings" + + "gopkg.in/yaml.v3" ) type BinaryFromImage struct { diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go index e64880be624..c5f52ff6330 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image.go @@ -1,13 +1,16 @@ package internal import ( + "encoding/json" "fmt" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" - "github.com/google/uuid" "os" "os/exec" "path/filepath" + + "github.com/google/uuid" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" ) func DownloadFromImage(dest string, config config.BinaryFromImage) error { @@ -19,9 +22,8 @@ func DownloadFromImage(dest string, config config.BinaryFromImage) error { if !isDownloadStale(config, hostPaths) { t.Skip("already exists") return nil - } else { - t.Update("stale, updating...") } + t.Update("stale, updating...") } if err := pullDockerImages(config.Images); err != nil { @@ -78,24 +80,62 @@ func pullDockerImages(images []config.Image) error { return nil } +type imageInspect struct { + OS string `json:"Os"` + Architecture string `json:"Architecture"` +} + +func (i imageInspect) Platform() string { + return fmt.Sprintf("%s/%s", i.OS, i.Architecture) +} + func pullDockerImage(imageReference, platform string) error { a := ui.Action{Msg: fmt.Sprintf("pull image %s (%s)", imageReference, platform)} a.Start() - cmd := exec.Command("docker", "image", "inspect", imageReference) - if err := cmd.Run(); err == nil { - a.Skip(fmt.Sprintf("docker image already exists %q", imageReference)) + matches, _, _ := checkArchitecturesMatch(imageReference, platform) + if matches { + a.Skip(fmt.Sprintf("docker image already exists %q", imageReference)) return nil } - cmd = exec.Command("docker", "pull", "--platform", platform, imageReference) + cmd := exec.Command("docker", "pull", "--platform", platform, imageReference) err := cmd.Run() + if err != nil { + a.Done(err) + return err + } + + matches, gotPlatform, err := checkArchitecturesMatch(imageReference, platform) + if !matches && err == nil { + err = fmt.Errorf("image %q pulled but does not match expected platform %q != %q", imageReference, platform, gotPlatform) + } a.Done(err) return err } +func checkArchitecturesMatch(imageReference, platform string) (bool, string, error) { + cmd := exec.Command("docker", "image", "inspect", imageReference) + out, err := cmd.CombinedOutput() + if err != nil { + return false, "", err + } + + var inspect []imageInspect + if err := json.Unmarshal(out, &inspect); err != nil { + return false, "", fmt.Errorf("unable to unmarshal image inspect: %w", err) + } + + if len(inspect) != 1 { + return false, "", fmt.Errorf("expected 1 image inspect, got %d", len(inspect)) + } + gotPlatform := inspect[0].Platform() + + return gotPlatform == platform, gotPlatform, nil +} + func copyBinariesFromDockerImages(config config.BinaryFromImage, destination string) (err error) { for _, image := range config.Images { if err := copyBinariesFromDockerImage(config, destination, image); err != nil { @@ -116,7 +156,7 @@ func copyBinariesFromDockerImage(config config.BinaryFromImage, destination stri defer func() { cmd := exec.Command("docker", "rm", containerName) - cmd.Run() + cmd.Run() // nolint:errcheck }() for i, destinationPath := range config.AllStorePathsForImage(image, destination) { @@ -130,7 +170,6 @@ func copyBinariesFromDockerImage(config config.BinaryFromImage, destination stri } func copyBinaryFromContainer(containerName, containerPath, destinationPath, fingerprint string) (err error) { - a := ui.Action{Msg: fmt.Sprintf("extract %s", containerPath)} a.Start() @@ -142,14 +181,15 @@ func copyBinaryFromContainer(containerName, containerPath, destinationPath, fing return err } - cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", containerName, containerPath), destinationPath) + cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", containerName, containerPath), destinationPath) // nolint:gosec + // reason for gosec exception: this is for processing test fixtures only, not used in production if err := cmd.Run(); err != nil { return err } // capture fingerprint file fingerprintPath := destinationPath + ".fingerprint" - if err := os.WriteFile(fingerprintPath, []byte(fingerprint), 0644); err != nil { + if err := os.WriteFile(fingerprintPath, []byte(fingerprint), 0600); err != nil { return fmt.Errorf("unable to write fingerprint file: %w", err) } diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go index f07fc5dc0e2..5c8922e4b76 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries.go @@ -2,11 +2,12 @@ package internal import ( "fmt" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" "os" "path/filepath" "sort" "strings" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" ) type Entries map[LogicalEntryKey]EntryInfo @@ -89,7 +90,6 @@ func ListAllBinaries(appConfig config.Application) (Entries, error) { } func ListAllEntries(appConfig config.Application) (Entries, error) { - snippets, err := allFilePaths(appConfig.SnippetPath) if err != nil { return nil, fmt.Errorf("unable to list snippets: %w", err) @@ -155,7 +155,7 @@ func getLogicalKey(managedBinaryPath string) (*LogicalEntryKey, error) { // infer the logical key from the path alone: name/version/platform/filename items := SplitFilepath(managedBinaryPath) - if len(items) != 4 { + if len(items) < 4 { return nil, fmt.Errorf("invalid managed binary path: %q", managedBinaryPath) } @@ -163,7 +163,7 @@ func getLogicalKey(managedBinaryPath string) (*LogicalEntryKey, error) { OrgName: items[0], Version: items[1], Platform: items[2], - Filename: items[3], + Filename: filepath.Join(items[3:]...), }, nil } diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go index 171db4a3bdb..d70de3e23b0 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata.go @@ -2,9 +2,10 @@ package internal import ( "fmt" - "gopkg.in/yaml.v3" "os" "strings" + + "gopkg.in/yaml.v3" ) type SnippetMetadata struct { diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go index 00d6381e845..6f24bf506ca 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/action.go @@ -30,7 +30,6 @@ func (a Action) Skip(newMsg ...string) { func (a Action) Done(err error) { goToPreviousLineStart() if err != nil { - fmt.Printf(" %s✗%s %s%s%s\n", red+bold, reset, red, a.Msg, reset) var exitError *exec.ExitError diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go index b551ead9ed1..a914e74b11e 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui/binary_list.go @@ -2,10 +2,11 @@ package ui import ( "fmt" + "os" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "os" ) var quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/main.go b/syft/pkg/cataloger/binary/test-fixtures/manager/main.go index d6b35fe3fcd..14b23f0da97 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/main.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/main.go @@ -2,9 +2,10 @@ package main import ( "fmt" + "os" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli" "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/ui" - "os" ) func main() { diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go index a95df4f6cfa..aa194b3a1d3 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go @@ -1,12 +1,14 @@ package testutil import ( - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" - "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" - "github.com/stretchr/testify/require" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal" + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" ) // SnippetOrBinary returns the path to either the binary or the snippet for the given logical entry key. @@ -96,5 +98,4 @@ func validateSnippet(t *testing.T, binaryPath, snippetPath string) { require.NoError(t, err) require.Equal(t, expected, metadata.FileSha256, "snippet shadows a binary with a different sha256") - } From 8006e9d8a0fef72469778f3b01a3d678d58735c1 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 22 Dec 2023 13:02:26 -0500 Subject: [PATCH 20/28] adjust gitignore to be more permissive to snippets Signed-off-by: Alex Goodman --- Taskfile.yaml | 2 +- .../test-fixtures/classifiers/snippets/.gitignore | 5 +++++ .../snippets/go-version-hint/1.15/any/VERSION | 1 + .../php-apache/8.2.1/linux-amd64/libphp.so | 12 ++++++++++++ .../3.8.16/linux-amd64/libpython3.8.so | Bin 0 -> 122 bytes .../1.67.1/linux-amd64/libstd-86aefecbddda356d.so | Bin 0 -> 350 bytes .../1.50.0/linux-amd64/libstd-6f77337c1826707d.so | Bin 0 -> 350 bytes .../1.67.1/linux-amd64/libstd-c6192dd4c4d410ac.so | Bin 0 -> 350 bytes 8 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/.gitignore create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/go-version-hint/1.15/any/VERSION create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-apache/8.2.1/linux-amd64/libphp.so create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/python-duplicates/3.8.16/linux-amd64/libpython3.8.so create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd-musl/1.67.1/linux-amd64/libstd-86aefecbddda356d.so create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/linux-amd64/libstd-6f77337c1826707d.so create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/linux-amd64/libstd-c6192dd4c4d410ac.so diff --git a/Taskfile.yaml b/Taskfile.yaml index a7bbfc66422..e6f9115c7be 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -173,7 +173,7 @@ tasks: check-binary-fixture-size: desc: Ensure that the binary test fixtures are not too large cmds: - - .github/scripts/check_binary_fixture_size.sh syft/pkg/cataloger/binary/test-fixtures/classifiers/positive + - .github/scripts/check_binary_fixture_size.sh syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets ## Testing tasks ################################# diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/.gitignore new file mode 100644 index 00000000000..0a755729c4f --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/.gitignore @@ -0,0 +1,5 @@ +# allow for lb patterns (rust, pytho, php and more) +!lib*.so + +# allow for go-hint file +!VERSION* \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/go-version-hint/1.15/any/VERSION b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/go-version-hint/1.15/any/VERSION new file mode 100644 index 00000000000..5bedbed9f95 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/go-version-hint/1.15/any/VERSION @@ -0,0 +1 @@ +go1.15-beta2 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-apache/8.2.1/linux-amd64/libphp.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-apache/8.2.1/linux-amd64/libphp.so new file mode 100644 index 00000000000..61906e9f658 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/php-apache/8.2.1/linux-amd64/libphp.so @@ -0,0 +1,12 @@ +name: libphp.so +offset: unknown +length: unknown +snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f +fileSha256: unknown + +### byte snippet to follow ### +%s' +%s,%s +X-Powered-By: PHP/8.2.1 +index pointer +PHP_VERSION diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/python-duplicates/3.8.16/linux-amd64/libpython3.8.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/python-duplicates/3.8.16/linux-amd64/libpython3.8.so new file mode 100644 index 0000000000000000000000000000000000000000..a1b0d2429480c7504bf546011a15435fd900d33c GIT binary patch literal 122 zcmY$$P0Y+w$jmEAEh;EV1(FQOiFxWJ3b~2dsS0VCMa3lwB^gDji75;|&hd^uJ`Bcs z7J7ze48 RRE2=Z5D$O9aK|7&E&$|XC_exI literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd-musl/1.67.1/linux-amd64/libstd-86aefecbddda356d.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd-musl/1.67.1/linux-amd64/libstd-86aefecbddda356d.so new file mode 100644 index 0000000000000000000000000000000000000000..045a097a548e7cf491f63067ea96666d57420665 GIT binary patch literal 350 zcmXw#y>7!W5Q8)ODGtbz46$|kIZK(^J)Ht{mvpDZfNKYq+ahmY7idS4z(;~p{E#z@ zL#ivEJVhyz6EjESh}$|hb17BUGkE3DDr!cVZtMLFs8my@;qj35dXL)C8I(sPAIOTM zm?gLpa$&0s_1QAzjJ;7VIBSZtUbqBRylC%C45FNG$}q}5Op<8APuGG>EFO zO96c->?8I?ZxB{Uh1ghBTXFK(Cl{M$zu!Z8t^$8EVV$AO<2ZlA7B#O|(5*2o?(!I? z8(c2$Z*W*X>zd(9mO9K6U`u_Aa9|g`PKh;WrOip{lj@Jh-FDj&muI(q*VQF=n{Jod Kz5E(2dqY3Mhh*UZ literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/linux-amd64/libstd-6f77337c1826707d.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/linux-amd64/libstd-6f77337c1826707d.so new file mode 100644 index 0000000000000000000000000000000000000000..fe4990e713bbc78b858bd825abee92879f93017f GIT binary patch literal 350 zcmXw#&1%Ci5QN>UpF)sJa)`ZJdG+IGNKb{{wfad+T{~DxOWwYwv~wF~7=~~0pJ^2H z#HGfw&c-NZg0!d&nAnxIU2`s}jzTMCH4W{YmTSF@LISj9neO*g-)`~h^_NDn%nEi4U zm&^OBIBt(pgZN5Ynbt+fj-Uf^3~b0piYyRNo&nDiPN!zaw)ySZ@9g@rg?{(lR+>KC KQutUOwf_N-(qk+D literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/linux-amd64/libstd-c6192dd4c4d410ac.so b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/linux-amd64/libstd-c6192dd4c4d410ac.so new file mode 100644 index 0000000000000000000000000000000000000000..300b9f5dee78e83bc358342de9225a3bb3ee49e7 GIT binary patch literal 350 zcmXw#J8r`;5JX+`6brbq3rYNcROJ9p6(IeROWFct$q?xTzI_xRDQDhbFqj4Jd4ze2 zwNaO>2$D!AO Date: Fri, 22 Dec 2023 13:23:54 -0500 Subject: [PATCH 21/28] add rust darwin snippets Signed-off-by: Alex Goodman --- .../pkg/cataloger/binary/test-fixtures/.gitignore | 1 + .../1.50.0/darwin/libstd-f6f9eec1635e636a.dylib | Bin 0 -> 301 bytes .../1.67.1/darwin/libstd-16f2b65e77054c42.dylib | Bin 0 -> 301 bytes 3 files changed, 1 insertion(+) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/darwin/libstd-f6f9eec1635e636a.dylib create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/darwin/libstd-16f2b65e77054c42.dylib diff --git a/syft/pkg/cataloger/binary/test-fixtures/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/.gitignore index 5478b80ea6c..e1d59c126c8 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/.gitignore +++ b/syft/pkg/cataloger/binary/test-fixtures/.gitignore @@ -4,6 +4,7 @@ cache.fingerprint # allow for lb patterns (rust, pytho, php and more) !lib*.so +!lib*.dylib # allow for go-hint file !VERSION* \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/darwin/libstd-f6f9eec1635e636a.dylib b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.50.0/darwin/libstd-f6f9eec1635e636a.dylib new file mode 100644 index 0000000000000000000000000000000000000000..d6a07ecf7453770d962153ded3c6b93c9e6e8ef2 GIT binary patch literal 301 zcmYMtJx;?g7{>7w!Q!nqu#^RcME0BbBe@0(t3TU_)UJrDsyE;SOe_d7R4nWuRZ4Ec z1-Jqks2CsprRNva#NJx$LB(~^;r;A^7=QxBD+`?tZ|lsw^dVfO$}R`xIw>29AeD? literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/darwin/libstd-16f2b65e77054c42.dylib b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/rust-libstd/1.67.1/darwin/libstd-16f2b65e77054c42.dylib new file mode 100644 index 0000000000000000000000000000000000000000..c18029f935590bcacc2704851ef965cd258351d6 GIT binary patch literal 301 zcmYL@Jx;?g7>3g#F?iz!ma@Q*$o`zqj&lu8V8=F%2uVd;Ro!6Z1XL`D6VQ!;&AAvE zs5-uS-oL>-`G)qLouVta3}7XCr5ILi1*JPIgp46fJ~nhWJPzZ2AiW4FT_fY*L=Y8nL>Cw;4G0xOXShS_fP}91=d$xA z$@84rL-h22jmD9NvG2z{T@ZQRO)<-|>ny#cQ~JqQWIp+p@=~gj(_4q82D_9~U?2(x s1ygtP>w5mWN+0tv9hcb4{5^lB_w>8U);EO^p|!%W Date: Fri, 22 Dec 2023 14:34:37 -0500 Subject: [PATCH 22/28] skip tests that are missing full binaries Signed-off-by: Alex Goodman --- .../test-fixtures/manager/testutil/snippet_or_binary.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go index aa194b3a1d3..609169182f2 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go @@ -63,6 +63,10 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { break } + if v.SnippetPath != "" && !v.IsConfigured { + t.Skip("no binary found, but is covered by a snippet. Please add this case to the 'binary/test-fixtures/config.yaml' and recreate the snippet") + } + t.Fatalf("no binary found for %q", path) } } From ac1c35e72e7bd361b97fb007af05092667254a4c Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 5 Jan 2024 12:06:25 -0500 Subject: [PATCH 23/28] address PR feedback Signed-off-by: Alex Goodman --- .github/scripts/check_binary_fixture_size.sh | 11 +-- .../binary/{test-fixtures => }/README.md | 65 +++++++++--- syft/pkg/cataloger/binary/cataloger_test.go | 10 +- .../binary/test-fixtures/capture-snippet.sh | 99 +++++++++++-------- .../binary/test-fixtures/config.yaml | 5 +- 5 files changed, 128 insertions(+), 62 deletions(-) rename syft/pkg/cataloger/binary/{test-fixtures => }/README.md (68%) diff --git a/.github/scripts/check_binary_fixture_size.sh b/.github/scripts/check_binary_fixture_size.sh index 4518d3bc5eb..4c9a0f99692 100755 --- a/.github/scripts/check_binary_fixture_size.sh +++ b/.github/scripts/check_binary_fixture_size.sh @@ -3,7 +3,6 @@ # current limit for fixture size size=600 -# Check if a directory is provided as an argument if [ $# -eq 0 ]; then echo "Usage: $0 " exit 1 @@ -14,28 +13,24 @@ directory="$1" # Remove trailing slash using parameter expansion directory="${directory%/}" -# Check if the directory exists if [ ! -d "$directory" ]; then echo "Directory not found: $directory" exit 1 fi -# Use find to locate all files in the directory and its subdirectories found_large_files=0 while IFS= read -r -d '' file; do - # Check if the file size is greater than 600 bytes if [ $(wc -c < "$file") -gt $size ]; then - echo "File $file is greater than 600 bytes." + echo "File $file is greater than ${size} bytes." found_large_files=1 fi done < <(find "$directory" -type f -print0) -# Check if any large files were found if [ "$found_large_files" -eq 1 ]; then - echo "Script failed: Some files are greater than 600 bytes." + echo "Script failed: Some files are greater than ${size} bytes." exit 1 else - echo "All files in $directory and its subdirectories are 600 bytes or smaller. Script passed." + echo "All files in $directory and its subdirectories are ${size} bytes or smaller. Check passed." exit 0 fi diff --git a/syft/pkg/cataloger/binary/test-fixtures/README.md b/syft/pkg/cataloger/binary/README.md similarity index 68% rename from syft/pkg/cataloger/binary/test-fixtures/README.md rename to syft/pkg/cataloger/binary/README.md index 8b7d42a03a3..002167e8dfd 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/README.md +++ b/syft/pkg/cataloger/binary/README.md @@ -1,4 +1,12 @@ -# Binary cataloger test fixtures +# Adding tests for the Binary cataloger + +> [!TIP] +> **TL;DR** to add a test for a new classifier (relative to the `test-fixtures` directory): +> 1. add a new entry to `config.yaml` to track where to get the binary from (verify the entry with `make list`) +> 2. run `make download` to get the binary +> 3. run `go run ./manager add-snippet` and follow the prompts (use `/` to search) +> 4. add a new test case to `Test_Cataloger_PositiveCases` in `cataloger_test.go` + To test the binary cataloger we run it against a set of files ("test fixtures"). There are two kinds of test fixtures: @@ -28,6 +36,40 @@ And use tooling to list and manage the fixtures: There is a `config.yaml` that tracks all binaries that the tests can use. This makes it possible to download it at any time from a hosted source. Today the only method allowed is to download a container image and extract files out. +Here is an example entry in `config.yaml` for a binary reference from a container image: + +```yaml +from-images: + + # note the group-name is assumed from the single binary name extracted: "redis-server" + - version: 7.2.3 + images: + # note we're pulling the same binary from multiple images (representing different architectures) + - ref: redis:7.2.3@sha256:d4c84914b872521e215f77d8845914c2268a96b0e35bacd5691e1f5e1f88b500 + platform: linux/amd64 + - ref: redis:7.2.3@sha256:a0a0c38b31011b813cddf78d997f8ccba13019c27efd386984b0cfc1e4b618ff + platform: linux/arm64 + # the paths to extract from the binary... + paths: + - /usr/local/bin/redis-server + + # since there are multiple binaries in the image, we need to specify the group-name + - name: ruby-bullseye-shared-libs + version: 2.7.7 + images: + - ref: ruby:2.7.7-bullseye@sha256:055191740a063f33fef1f09423e5ed8f91143aae62a3772a90910118464c5120 + platform: linux/amd64 + paths: + - /usr/local/bin/ruby + - /usr/local/lib/libruby.so.2.7.7 + - /usr/local/lib/libruby.so.2.7 +``` + + +> [!NOTE] +> You will need a system with `go`, `bash`, `strings`, and `xxd` installed to capture test snippets. + + ## Testing The test cases have been setup to allow testing against full binaries or a mix of both (default). @@ -39,16 +81,7 @@ go test -must-use-full-binaries ./syft/pkg/cataloger/binary/test-fixtures/... ## Adding a new test fixture -### Adding a full binary - -1. Add a new entry to `config.yaml` with the following fields - - if you are adding a single binary, the `name` field does not need to be specified - - the `name` field is useful for distinguishing a quality about the binary (e.g. `java` vs `java-jre-ibm`) - -2. Run `make download` and ensure your new binary is downloaded - - -### Adding a snippet +### Adding a snippet (recommended) Even if you are adding a snippet, it is best practice to: @@ -66,6 +99,16 @@ Even if you are adding a snippet, it is best practice to: - `` is the name of the binary (e.g. `curl`) or the name in `config.yaml` if specified - note that your test does not know about if it's running against a snippet or a full binary + +### Adding a full binary + +1. Add a new entry to `config.yaml` with the following fields + - if you are adding a single binary, the `name` field does not need to be specified + - the `name` field is useful for distinguishing a quality about the binary (e.g. `java` vs `java-jre-ibm`) + +2. Run `make download` and ensure your new binary is downloaded + + ### Adding a custom snippet If you need to add a snippet that is not based off of a full binary, you can use the `capture-snippet.sh` script. diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 592a0dd8310..6b94dfcfc16 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -24,7 +24,11 @@ var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "fo func Test_Cataloger_PositiveCases(t *testing.T) { tests := []struct { - name string + name string + // logicalFixture is the logical path to the full binary or snippet. This is relative to the test-fixtures/classifiers/snippets + // or test-fixtures/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are + // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only + // full binaries are tested (no snippets), and if no binary is found the test will be skipped. logicalFixture string expected pkg.Package }{ @@ -811,6 +815,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) { t.Run(test.logicalFixture, func(t *testing.T) { c := NewCataloger() + // logicalFixture is the logical path to the full binary or snippet. This is relative to the test-fixtures/classifiers/snippets + // or test-fixtures/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are + // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only + // full binaries are tested (no snippets), and if no binary is found the test will be skipped. path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries) src, err := source.NewFromDirectoryPath(path) diff --git a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh index 2767682d326..426e3b87d46 100755 --- a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh +++ b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh @@ -70,61 +70,78 @@ fi PATTERN=${SEARCH_FOR:-$VERSION} +echo "Using binary file: $BINARY_FILE" +echo "Searching for pattern: $PATTERN" +echo "Capture length: $LENGTH bytes" +echo "Capture prefix length: $PREFIX_LENGTH bytes" + PATTERN_RESULTS=$(strings -t d "$BINARY_FILE" | grep "$PATTERN") +RESULT_COUNT=$(echo "$PATTERN_RESULTS" | wc -l) -# if there are multiple matches, prompt the user to select one -if [ $(echo "$PATTERN_RESULTS" | wc -l) -gt 1 ]; then - echo "Multiple string matches found in the binary:" - echo "" +CONTINUE_LOOP=true - # show result lines one at a time (in a numbered list) - # but only show everything after the first field (not the offset) - echo "$PATTERN_RESULTS" | cut -d ' ' -f 2- | nl -w 1 -s ') ' +while $CONTINUE_LOOP; do + # if there are multiple matches, prompt the user to select one + if [ $RESULT_COUNT -gt 1 ]; then + echo "Multiple string matches found in the binary:" + echo "" - echo "" - read -p "Please select a match: " SELECTION + # show result lines one at a time (in a numbered list) + # but only show everything after the first field (not the offset) + echo "$PATTERN_RESULTS" | cut -d ' ' -f 2- | nl -w 1 -s ') ' - # if the selection is not a number, exit - if ! [[ "$SELECTION" =~ ^[0-9]+$ ]]; then - echo "Invalid selection." - exit 1 - fi - # if the selection is out of bounds, exit - if [ "$SELECTION" -gt $(echo "$PATTERN_RESULTS" | wc -l) ]; then - echo "Invalid selection." - exit 1 - fi + echo "" + read -p "Please select a match: " SELECTION - # select the line from the results - PATTERN_RESULTS=$(echo "$PATTERN_RESULTS" | sed -n "${SELECTION}p") -fi + # if the selection is not a number, exit + if ! [[ "$SELECTION" =~ ^[0-9]+$ ]]; then + echo "Invalid selection." + exit 1 + fi -# search for the pattern in the binary file and capture the offset -OFFSET=$(echo "${PATTERN_RESULTS}" | cut -d ' ' -f 1) + # if the selection is out of bounds, exit + if [ "$SELECTION" -gt $(echo "$PATTERN_RESULTS" | wc -l) ]; then + echo "Invalid selection." + exit 1 + fi -if [ -z "$OFFSET" ]; then - echo "Pattern not found." - exit 1 -fi + # select the line from the results + SELECTED_RESULT=$(echo "$PATTERN_RESULTS" | sed -n "${SELECTION}p") + else + SELECTED_RESULT="$PATTERN_RESULTS" + fi + + # search for the pattern in the binary file and capture the offset + OFFSET=$(echo "${SELECTED_RESULT}" | cut -d ' ' -f 1) + + if [ -z "$OFFSET" ]; then + echo "Pattern not found." + exit 1 + fi -# adjust the offset to capture prefix length before the match -OFFSET=$(expr "$OFFSET" - "$PREFIX_LENGTH") + # adjust the offset to capture prefix length before the match + OFFSET=$(expr "$OFFSET" - "$PREFIX_LENGTH") -# use xxd to capture the specified length from the calculated offset -SNIPPET=$(xxd -l "$LENGTH" -s "$OFFSET" "$BINARY_FILE") + # use xxd to capture the specified length from the calculated offset + SNIPPET=$(xxd -l "$LENGTH" -s "$OFFSET" "$BINARY_FILE") -# display the output and prompt the user -echo "" -echo "$SNIPPET" -echo "" -read -p "Does this snippet capture what you need? (Y/n) " RESPONSE -RESPONSE=${RESPONSE:-y} + # display the output and prompt the user + echo "" + echo "$SNIPPET" + echo "" + read -p "Does this snippet capture what you need? (Y/n/q) " RESPONSE -if [ "$RESPONSE" != "y" ]; then + RESPONSE=${RESPONSE:-y} + + if [ "$RESPONSE" == "y" ]; then + CONTINUE_LOOP=false + elif [ $RESULT_COUNT -eq 1 ] || [ "$RESPONSE" == "q" ]; then echo "Exiting with no action taken." - exit 1 -fi + exit 0 + fi + +done go run ./manager write-snippet "$BINARY_FILE" --offset "$OFFSET" --length "$LENGTH" --name "$GROUP_NAME" --version "$VERSION" diff --git a/syft/pkg/cataloger/binary/test-fixtures/config.yaml b/syft/pkg/cataloger/binary/test-fixtures/config.yaml index 958f67787ed..f3ab97a9a61 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/config.yaml +++ b/syft/pkg/cataloger/binary/test-fixtures/config.yaml @@ -1,7 +1,10 @@ download-path: classifiers/bin snippet-path: classifiers/snippets -# this section is for pulling entire binaries out of container images +# this section is for pulling entire binaries out of container images. You can pull multiple binaries from one or more +# images, where a list of images is used to enumerate multiple platforms for the same logical image. The group name +# for the test fixture defaults to the path of the binary extracted. If there is more than one binary extracted from +# the image(s) then you will need to specify a group name explicitly. from-images: # from the positive snippets... From 1860b35be7d7c20537f132aa3815cc4e6bce4344 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 5 Jan 2024 14:54:04 -0500 Subject: [PATCH 24/28] add tests for binary test fixture manager Signed-off-by: Alex Goodman --- syft/pkg/cataloger/binary/README.md | 11 +- .../cataloger/binary/test-fixtures/Makefile | 3 + .../internal/config/application_test.go | 67 ++++++ .../internal/config/binary_from_image_test.go | 191 ++++++++++++++++++ .../app-configs/bad-image-collision.yaml | 14 ++ .../bad-implicit-name-collision.yaml | 19 ++ .../bad-missing-image-platform.yaml | 12 ++ .../app-configs/bad-missing-image-ref.yaml | 11 + .../app-configs/bad-missing-image.yaml | 9 + .../app-configs/bad-missing-paths.yaml | 10 + .../app-configs/bad-missing-version.yaml | 10 + .../testdata/app-configs/bad-no-name.yaml | 12 ++ .../config/testdata/app-configs/valid-1.yaml | 19 ++ .../config/testdata/app-configs/valid-2.yaml | 19 ++ .../internal/download_from_image_test.go | 62 ++++++ .../manager/internal/list_entries_test.go | 116 +++++++++++ .../manager/internal/snippet_metadata_test.go | 70 +++++++ .../manager/internal/testdata/.gitignore | 1 + .../bin/busybox/1.3.6/linux-amd64/busybox | 1 + .../bin/busybox/1.3.6/linux-arm64/busybox | 1 + .../busybox/1.3.6/linux-amd64/busybox | 8 + .../busybox/1.3.6/linux-arm64/busybox | 8 + .../postgres/9.6.10/linux-amd64/postgres | 8 + .../test-fixtures/manager/internal/utils.go | 3 +- .../manager/internal/utils_test.go | 49 +++++ .../manager/testutil/snippet_or_binary.go | 30 ++- .../testutil/snippet_or_binary_test.go | 63 ++++++ 27 files changed, 810 insertions(+), 17 deletions(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-image-collision.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-implicit-name-collision.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-platform.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-ref.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-paths.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-version.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-no-name.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-1.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-2.yaml create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/.gitignore create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-amd64/busybox create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-arm64/busybox create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-amd64/busybox create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-arm64/busybox create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/postgres/9.6.10/linux-amd64/postgres create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary_test.go diff --git a/syft/pkg/cataloger/binary/README.md b/syft/pkg/cataloger/binary/README.md index 002167e8dfd..e42f456ade8 100644 --- a/syft/pkg/cataloger/binary/README.md +++ b/syft/pkg/cataloger/binary/README.md @@ -1,11 +1,12 @@ # Adding tests for the Binary cataloger > [!TIP] -> **TL;DR** to add a test for a new classifier (relative to the `test-fixtures` directory): -> 1. add a new entry to `config.yaml` to track where to get the binary from (verify the entry with `make list`) -> 2. run `make download` to get the binary -> 3. run `go run ./manager add-snippet` and follow the prompts (use `/` to search) -> 4. add a new test case to `Test_Cataloger_PositiveCases` in `cataloger_test.go` +> **TL;DR** to add a test for a new classifier: +> 1. head to the correct directory: `cd test-fixtures` +> 2. add a new entry to `config.yaml` to track where to get the binary from (verify the entry with `make list`) +> 3. run `make download` to get the binary +> 4. run `make add-snippet` and follow the prompts (use `/` to search) +> 5. add a new test case to `Test_Cataloger_PositiveCases` in `../cataloger_test.go` To test the binary cataloger we run it against a set of files ("test fixtures"). There are two kinds of test fixtures: diff --git a/syft/pkg/cataloger/binary/test-fixtures/Makefile b/syft/pkg/cataloger/binary/test-fixtures/Makefile index 5d752c2be3b..3e8efed9468 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/Makefile +++ b/syft/pkg/cataloger/binary/test-fixtures/Makefile @@ -13,6 +13,9 @@ download: ## download only binaries that are not covered by a snippet download-all: ## download all managed binaries go run ./manager download +add-snippet: ## add a new snippet from an existing binary + go run ./manager add-snippet + cache.fingerprint: ## prints the sha256sum of the any input to the download command (to determine if there is a cache miss) @cat ./config.yaml | sha256sum | awk '{print $$1}' | tee cache.fingerprint diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application_test.go new file mode 100644 index 00000000000..f892382a166 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/application_test.go @@ -0,0 +1,67 @@ +package config + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadAndValidate(t *testing.T) { + tests := []struct { + name string + expectErr bool + }{ + { + name: "valid-1.yaml", + }, + { + name: "valid-2.yaml", + }, + { + name: "bad-implicit-name-collision.yaml", + expectErr: true, + }, + { + name: "bad-missing-version.yaml", + expectErr: true, + }, + { + name: "bad-missing-image.yaml", + expectErr: true, + }, + { + name: "bad-no-name.yaml", + expectErr: true, + }, + { + name: "bad-missing-image-platform.yaml", + expectErr: true, + }, + { + name: "bad-missing-image-ref.yaml", + expectErr: true, + }, + { + name: "bad-image-collision.yaml", + expectErr: true, + }, + { + name: "bad-missing-paths.yaml", + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + appConfig, err := read(filepath.Join("testdata", "app-configs", tt.name)) + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, appConfig) + assert.NoError(t, appConfig.Validate()) + } + }) + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image_test.go new file mode 100644 index 00000000000..8d76d5a2b29 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/binary_from_image_test.go @@ -0,0 +1,191 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKey(t *testing.T) { + tests := []struct { + name string + binary BinaryFromImage + expected string + }{ + { + name: "gocase", + binary: BinaryFromImage{ + GenericName: "test", + Version: "1.0", + }, + expected: "test:1.0", + }, + { + name: "binary name", + binary: BinaryFromImage{ + Version: "1.0", + PathsInImage: []string{ + "path/to/test", + }, + }, + expected: "test:1.0", + }, + { + name: "first binary name", + binary: BinaryFromImage{ + Version: "1.0", + PathsInImage: []string{ + "path/to/test", + "path/to/nothing", + }, + }, + expected: "test:1.0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.binary.Key()) + }) + } +} + +func TestName(t *testing.T) { + tests := []struct { + name string + binary BinaryFromImage + expected string + }{ + { + name: "given name", + binary: BinaryFromImage{ + GenericName: "given", + PathsInImage: []string{ + "path/to/test", + }, + }, + expected: "given", + }, + { + name: "binary name", + binary: BinaryFromImage{ + PathsInImage: []string{ + "path/to/test", + }, + }, + expected: "test", + }, + { + name: "first binary name", + binary: BinaryFromImage{ + PathsInImage: []string{ + "path/to/test", + "path/to/nothing", + }, + }, + expected: "test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.binary.Name()) + }) + } +} + +func TestAllStorePaths(t *testing.T) { + tests := []struct { + name string + binary BinaryFromImage + dest string + expected []string + }{ + { + name: "gocase", + binary: BinaryFromImage{ + GenericName: "test", + Version: "1.0", + Images: []Image{ + { + Reference: "ref1", + Platform: "platform1", + }, + { + Reference: "ref2", + Platform: "platform2", + }, + }, + PathsInImage: []string{ + "path/to/test1", + "path/to/test2", + }, + }, + dest: "dest", + expected: []string{ + "dest/test/1.0/platform1/test1", + "dest/test/1.0/platform1/test2", + "dest/test/1.0/platform2/test1", + "dest/test/1.0/platform2/test2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.binary.AllStorePaths(tt.dest)) + }) + } +} + +func TestPlatformAsValue(t *testing.T) { + tests := []struct { + name string + platform string + expected string + }{ + { + name: "gocase", + platform: "platform/test", + expected: "platform-test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, PlatformAsValue(tt.platform)) + }) + } +} + +func TestFingerprint(t *testing.T) { + tests := []struct { + name string + binary BinaryFromImage + expected string + }{ + { + name: "gocase", + binary: BinaryFromImage{ + GenericName: "test", + Version: "1.0", + Images: []Image{ + { + Reference: "ref", + Platform: "platform", + }, + }, + PathsInImage: []string{ + "path/to/test", + }, + }, + expected: "54ed081c07e4eba031afed4c04315cf96047822196473971be98d0769a0e3645", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.binary.Fingerprint()) + }) + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-image-collision.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-image-collision.yaml new file mode 100644 index 00000000000..3ff69e2eb11 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-image-collision.yaml @@ -0,0 +1,14 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + paths: + - /bin/[ diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-implicit-name-collision.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-implicit-name-collision.yaml new file mode 100644 index 00000000000..82b2b2dd1eb --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-implicit-name-collision.yaml @@ -0,0 +1,19 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: bash # !important + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + paths: + - /bin/[ + + - version: 1.36.1 # !important + images: + - ref: bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927 + platform: linux/amd64 + paths: + - /usr/local/bin/bash diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-platform.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-platform.yaml new file mode 100644 index 00000000000..b101265f504 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-platform.yaml @@ -0,0 +1,12 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + paths: + - /bin/[ + diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-ref.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-ref.yaml new file mode 100644 index 00000000000..cc70d2cc686 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image-ref.yaml @@ -0,0 +1,11 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + images: + - platform: linux/amd64 + paths: + - /bin/[ diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image.yaml new file mode 100644 index 00000000000..b5efbc94cd4 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-image.yaml @@ -0,0 +1,9 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + paths: + - /bin/[ diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-paths.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-paths.yaml new file mode 100644 index 00000000000..b5aa0c66e74 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-paths.yaml @@ -0,0 +1,10 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-version.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-version.yaml new file mode 100644 index 00000000000..d251044dfe5 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-missing-version.yaml @@ -0,0 +1,10 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - images: + - ref: bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927 + platform: linux/amd64 + paths: + - /usr/local/bin/bash diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-no-name.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-no-name.yaml new file mode 100644 index 00000000000..2519bcde5d5 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/bad-no-name.yaml @@ -0,0 +1,12 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + paths: + - /bin/[ + - /bin/busybox diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-1.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-1.yaml new file mode 100644 index 00000000000..cde519fb11f --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-1.yaml @@ -0,0 +1,19 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: busybox + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + paths: + - /bin/[ + + - version: 5.1.16 + images: + - ref: bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927 + platform: linux/amd64 + paths: + - /usr/local/bin/bash diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-2.yaml b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-2.yaml new file mode 100644 index 00000000000..28c86b3b76d --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config/testdata/app-configs/valid-2.yaml @@ -0,0 +1,19 @@ +download-path: classifiers/bin +snippet-path: classifiers/snippets + +from-images: + + - name: bash # !important + version: 1.36.1 + images: + - ref: busybox:1.36.1@sha256:058f0df5310fbbbfea7e81a3a3e2b4bf3452438ec841138d170e170adbbd27a4 + platform: linux/amd64 + paths: + - /bin/[ + + - version: 5.1.16 + images: + - ref: bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927 + platform: linux/amd64 + paths: + - /usr/local/bin/bash diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image_test.go new file mode 100644 index 00000000000..ca62ea5476a --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/download_from_image_test.go @@ -0,0 +1,62 @@ +package internal + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" +) + +func TestIsDownloadStale(t *testing.T) { + + cases := []struct { + name string + fingerprint string + expected bool + }{ + { + name: "no fingerprint", + fingerprint: "", + expected: true, + }, + { + name: "fingerprint matches", + // this is the fingerprint for config in the loop body + fingerprint: "5177d458eaca031ea16fa707841043df2e31b89be6bae7ea41290aa32f0251a6", + expected: false, + }, + { + name: "fingerprint does not match", + fingerprint: "fingerprint", + expected: true, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + binaryPath := filepath.Join(t.TempDir(), "binary") + fh, err := os.Create(binaryPath + ".fingerprint") + require.NoError(t, err) + + fh.Write([]byte(tt.fingerprint)) + require.NoError(t, fh.Close()) + + cfg := config.BinaryFromImage{ + GenericName: "name", + Version: "version", + Images: []config.Image{{ + Reference: "ref", + Platform: "platform", + }}, + PathsInImage: []string{"path"}, + } + + assert.Equal(t, tt.expected, isDownloadStale(cfg, []string{binaryPath})) + }) + } + +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries_test.go new file mode 100644 index 00000000000..11ca8e3e50a --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/list_entries_test.go @@ -0,0 +1,116 @@ +package internal + +import ( + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/internal/config" +) + +func TestSortingLogicalEntryKeys(t *testing.T) { + keys := LogicalEntryKeys{ + {OrgName: "Org1", Version: "1.0", Platform: "Linux", Filename: "fileB"}, + {OrgName: "Org1", Version: "1.0", Platform: "Linux", Filename: "fileA"}, + {OrgName: "Org2", Version: "1.0", Platform: "Linux", Filename: "fileA"}, + {OrgName: "Org1", Version: "1.1", Platform: "Linux", Filename: "fileA"}, + } + + expected := LogicalEntryKeys{ + {OrgName: "Org1", Version: "1.0", Platform: "Linux", Filename: "fileA"}, + {OrgName: "Org1", Version: "1.0", Platform: "Linux", Filename: "fileB"}, + {OrgName: "Org1", Version: "1.1", Platform: "Linux", Filename: "fileA"}, + {OrgName: "Org2", Version: "1.0", Platform: "Linux", Filename: "fileA"}, + } + + sort.Sort(keys) + + assert.Equal(t, expected, keys) +} + +func TestListAllBinaries(t *testing.T) { + + appConfig := config.Application{ + DownloadPath: filepath.Join("testdata", "bin"), + SnippetPath: filepath.Join("testdata", "snippets"), + FromImages: []config.BinaryFromImage{ + { + Version: "1.3.6", + Images: []config.Image{ + { + Reference: "ref-1", + Platform: "linux/amd64", + }, + // this makes it not configured + //{ + // Reference: "ref-2", + // Platform: "linux/arm64", + //}, + }, + PathsInImage: []string{ + "/usr/local/bin/busybox", + }, + }, + }, + } + + entries, err := ListAllBinaries(appConfig) + + require.NoError(t, err) + require.Len(t, entries, 2) + + assert.Equal(t, + Entries{ + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: ""}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: ""}, + }, + entries, + ) + +} + +func TestListAllEntries(t *testing.T) { + + appConfig := config.Application{ + DownloadPath: filepath.Join("testdata", "bin"), + SnippetPath: filepath.Join("testdata", "snippets"), + FromImages: []config.BinaryFromImage{ + { + Version: "1.3.6", + Images: []config.Image{ + { + Reference: "ref-1", + Platform: "linux/amd64", + }, + // this makes it not configured + //{ + // Reference: "ref-2", + // Platform: "linux/arm64", + //}, + }, + PathsInImage: []string{ + "/usr/local/bin/busybox", + }, + }, + }, + } + + entries, err := ListAllEntries(appConfig) + + require.NoError(t, err) + require.Len(t, entries, 3) + + assert.Equal(t, + Entries{ + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-amd64/busybox"}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-arm64/busybox"}, + // note the standalone snippet! + LogicalEntryKey{OrgName: "postgres", Version: "9.6.10", Platform: "linux-amd64", Filename: "postgres"}: EntryInfo{IsConfigured: false, BinaryPath: "", SnippetPath: "testdata/snippets/postgres/9.6.10/linux-amd64/postgres"}, + }, + entries, + ) + +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata_test.go new file mode 100644 index 00000000000..8e8058f4fbf --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/snippet_metadata_test.go @@ -0,0 +1,70 @@ +package internal + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReadSnippetMetadata(t *testing.T) { + tests := []struct { + name string + content string + createFile bool + expectedError bool + expectedResult *SnippetMetadata + }{ + { + name: "valid metadata", + content: "name: test\noffset: 10\nlength: 20\nsnippetSha256: abcd\nfileSha256: efgh\n### byte snippet to follow ###\n", + expectedError: false, + expectedResult: &SnippetMetadata{Name: "test", Offset: 10, Length: 20, SnippetSha256: "abcd", FileSha256: "efgh"}, + }, + { + name: "invalid format", + content: "invalid content", + expectedError: true, + }, + { + name: "Empty content", + content: "", + createFile: true, + expectedError: true, + }, + { + name: "no path", + expectedError: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var path string + if tc.content != "" || tc.createFile { + path = createTestFile(t, tc.content) + } + + result, err := ReadSnippetMetadata(path) + if tc.expectedError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + } + }) + } +} + +func createTestFile(t *testing.T, content string) string { + t.Helper() + file, err := os.CreateTemp(t.TempDir(), "syft-test-snippetMetadata") + require.NoError(t, err) + if len(content) > 0 { + _, err = file.WriteString(content) + require.NoError(t, err) + } + require.NoError(t, file.Close()) + return file.Name() +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/.gitignore new file mode 100644 index 00000000000..f5135fa6b60 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/.gitignore @@ -0,0 +1 @@ +!bin \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-amd64/busybox b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-amd64/busybox new file mode 100644 index 00000000000..3cee8c64039 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-amd64/busybox @@ -0,0 +1 @@ +fake contents 1 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-arm64/busybox b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-arm64/busybox new file mode 100644 index 00000000000..d3772c56395 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/bin/busybox/1.3.6/linux-arm64/busybox @@ -0,0 +1 @@ +fake contents 2 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-amd64/busybox b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-amd64/busybox new file mode 100644 index 00000000000..954efcf56bf --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-amd64/busybox @@ -0,0 +1,8 @@ +name: busybox +offset: 890794 +length: 6 +snippetSha256: 1cb0df35e77d61622940971e9dc8387719b37dbfe905034f7c89c730d45477c2 +fileSha256: 5c44a88a3eaf375f58715829fe187b6eaeb2efcd41bdb77595ae732fddfd4959 + +### byte snippet to follow ### +fake 1 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-arm64/busybox b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-arm64/busybox new file mode 100644 index 00000000000..67900e7e4fd --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/busybox/1.3.6/linux-arm64/busybox @@ -0,0 +1,8 @@ +name: busybox +offset: 890794 +length: 6 +snippetSha256: 784ed38fdb1ad0498a159b15b710563ee43d1d88e361171b764e4f191888cbc0 +fileSha256: eaf375f5db77595ae732fddfd45c44a88a39598715829fe187b6eaeb2efcd41b + +### byte snippet to follow ### +fake 2 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/postgres/9.6.10/linux-amd64/postgres b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/postgres/9.6.10/linux-amd64/postgres new file mode 100644 index 00000000000..2a2ee9bd974 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/testdata/snippets/postgres/9.6.10/linux-amd64/postgres @@ -0,0 +1,8 @@ +name: postgres +offset: 78994 +length: 6 +snippetSha256: 08b237bd6e2701f6c169a2ce8b293ba431c2016f5b9b28597241dffeff100941 +fileSha256: efcd41bd715829fe187b6eaeb2ddfd4959b77595ae732f5c44a88a3eaf375f58 + +### byte snippet to follow ### +fake 3 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go index ab2578f8057..cc5eb2a8985 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "fmt" "io" - "os" "path/filepath" "strings" ) @@ -13,7 +12,7 @@ func SplitFilepath(path string) []string { return strings.Split(path, string(filepath.Separator)) } -func Sha256SumFile(f *os.File) (string, error) { +func Sha256SumFile(f io.ReadSeeker) (string, error) { _, err := f.Seek(0, io.SeekStart) if err != nil { return "", fmt.Errorf("unable to seek to start of file: %w", err) diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils_test.go new file mode 100644 index 00000000000..ab8d7a111e6 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/utils_test.go @@ -0,0 +1,49 @@ +package internal + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSplitFilepath(t *testing.T) { + tests := []struct { + path string + expected []string + }{ + {path: "a/b/c", expected: []string{"a", "b", "c"}}, + } + + for _, test := range tests { + result := SplitFilepath(test.path) + assert.Equal(t, test.expected, result) + } +} + +func TestSha256SumFile(t *testing.T) { + content := "test content" + reader := strings.NewReader(content) + + hash, err := Sha256SumFile(reader) + require.NoError(t, err, "Sha256SumFile should not return an error") + + expectedHash := Sha256SumBytes([]byte(content)) + assert.Equal(t, expectedHash, hash) +} + +func TestSha256SumBytes(t *testing.T) { + + tests := []struct { + input []byte + expected string + }{ + {input: []byte("hello"), expected: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}, + } + + for _, test := range tests { + result := Sha256SumBytes(test.input) + assert.Equal(t, test.expected, result) + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go index 609169182f2..6d26d8202fc 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go @@ -1,6 +1,7 @@ package testutil import ( + "errors" "os" "path/filepath" "testing" @@ -47,7 +48,7 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { if !requireBinary { if v.SnippetPath != "" { t.Logf("using snippet for %q", path) - validateSnippet(t, v.BinaryPath, v.SnippetPath) + require.NoError(t, validateSnippet(v.BinaryPath, v.SnippetPath)) fixturePath = v.SnippetPath break } @@ -80,26 +81,35 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { return filepath.Join("test-fixtures", filepath.Dir(fixturePath)) } -func validateSnippet(t *testing.T, binaryPath, snippetPath string) { - t.Helper() - +func validateSnippet(binaryPath, snippetPath string) error { // get a sha256 of the binary if _, err := os.Stat(binaryPath); err != nil { // no binary to validate against (this is ok) - return + return nil } metadata, err := internal.ReadSnippetMetadata(snippetPath) - require.NoError(t, err) + if err != nil { + return err + } if metadata == nil { - return + return nil } f, err := os.Open(binaryPath) - require.NoError(t, err) + if err != nil { + return err + } + expected, err := internal.Sha256SumFile(f) - require.NoError(t, err) + if err != nil { + return err + } + + if expected != metadata.FileSha256 { + return errors.New("snippet shadows a binary with a different sha256") + } - require.Equal(t, expected, metadata.FileSha256, "snippet shadows a binary with a different sha256") + return nil } diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary_test.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary_test.go new file mode 100644 index 00000000000..e79e7d7d97d --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary_test.go @@ -0,0 +1,63 @@ +package testutil + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateSnippet(t *testing.T) { + tests := []struct { + name string + binaryContent string + snippetContent string + expectError bool + }{ + { + name: "valid", + binaryContent: "testBinary", + snippetContent: `name: bash +offset: 992758 +length: 100 +snippetSha256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 +fileSha256: 88ef99f28b69a6d8113cba62011e574a5afb1e6d8e0f884699e2ced91e4d910c + +### byte snippet to follow ### +hello`, + expectError: false, + }, + { + name: "invalid", + binaryContent: "testBinary", + snippetContent: `name: bash +offset: 992758 +length: 100 +snippetSha256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 +fileSha256: 011e574a8ef9584699e2ced9189f28b69a6d8113cba62e4d910cafb1e6d8e0f8 + +### byte snippet to follow ### +hello`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + binaryFile, _ := os.CreateTemp(t.TempDir(), "binary") + binaryFile.WriteString(tt.binaryContent) + binaryFile.Close() + + snippetFile, _ := os.CreateTemp(t.TempDir(), "snippet") + snippetFile.WriteString(tt.snippetContent) + snippetFile.Close() + + err := validateSnippet(binaryFile.Name(), snippetFile.Name()) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} From 28aea2bf6cd776a107444f10f23f35ffd3fb20bb Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 5 Jan 2024 15:06:57 -0500 Subject: [PATCH 25/28] highlight rows that do not have binaries or snippets Signed-off-by: Alex Goodman --- .../manager/internal/cli/commands/list.go | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go index 6757794b3f6..3e3b76aab2a 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/charmbracelet/lipgloss" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" @@ -73,20 +74,35 @@ func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.Ent } } + var invalid bool + if bin == "" && snippet == "" { + invalid = true + } + t.AppendRow(table.Row{ - k.OrgName, - k.Version, - displayPlatform(k.Platform), - k.Filename, - isConfigured, - bin, - snippet, + renderCell(k.OrgName, invalid), + renderCell(k.Version, invalid), + renderCell(displayPlatform(k.Platform), invalid), + renderCell(k.Filename, invalid), + renderCell(isConfigured, invalid), + renderCell(bin, invalid), + renderCell(snippet, invalid), }) } return t.Render() } +var errorStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("9")) +var stdStyle = lipgloss.NewStyle() + +func renderCell(value string, invalid bool) string { + if invalid { + return errorStyle.Render(value) + } + return stdStyle.Render(value) +} + func displayPlatform(platform string) string { return strings.ReplaceAll(platform, "-", "/") } From 6d1a72f3f100ad1847829982c4aa271f8b19134f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 5 Jan 2024 16:11:08 -0500 Subject: [PATCH 26/28] bump fixture limit to 1K (found exceptions when adding snippets) Signed-off-by: Alex Goodman --- .github/scripts/check_binary_fixture_size.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/check_binary_fixture_size.sh b/.github/scripts/check_binary_fixture_size.sh index 4c9a0f99692..764824820cc 100755 --- a/.github/scripts/check_binary_fixture_size.sh +++ b/.github/scripts/check_binary_fixture_size.sh @@ -1,7 +1,7 @@ #!/bin/bash # current limit for fixture size -size=600 +size=1000 if [ $# -eq 0 ]; then echo "Usage: $0 " From 4f0a5ae7313895566ebe6a61e552fa76fe8074dc Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 5 Jan 2024 16:11:24 -0500 Subject: [PATCH 27/28] add redis and postgres snippets Signed-off-by: Alex Goodman --- .../postgres/15beta4/linux-amd64/postgres | Bin 0 -> 332 bytes .../redis-server/5.0.0/linux-amd64/redis-server | Bin 0 -> 486 bytes .../redis-server/6.0.16/linux-amd64/redis-server | Bin 0 -> 486 bytes .../redis-server/7.0.0/linux-amd64/redis-server | Bin 0 -> 486 bytes .../redis-server/7.0.14/linux-amd64/redis-server | Bin 0 -> 846 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/15beta4/linux-amd64/postgres create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/5.0.0/linux-amd64/redis-server create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/6.0.16/linux-amd64/redis-server create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/7.0.0/linux-amd64/redis-server create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/7.0.14/linux-amd64/redis-server diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/15beta4/linux-amd64/postgres b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/postgres/15beta4/linux-amd64/postgres new file mode 100644 index 0000000000000000000000000000000000000000..9d742f4800d9bb983e5fba0b123a29ec2d940f73 GIT binary patch literal 332 zcmXw!%}&HH421Wbr?9ja5Zc|?-K0s+965mY3F5|SE2TebgAf;Ytcr?g3Fpte`1$*C+;SNaA}Yqd?ovPil0fsAdSG6{)|#;9wIiG`6`W@|&m zUQ*D$s!_8eXE=r{3WwT9s!>pTO|42pmjAHCykD_plG-^*kAX(wy`B0XtG~%4^vr=3PcO{LJfb>)YE4pkXQD u3J=f1Lzwpc>u#LzymYZc)KB<$xaZGnly#Uo7>8{PrR@*r3%?IJUcLd#A!1|z literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/5.0.0/linux-amd64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/5.0.0/linux-amd64/redis-server new file mode 100644 index 0000000000000000000000000000000000000000..d301ebb183a29ae4c8e67ed3b9dcbf6ab9fe67bf GIT binary patch literal 486 zcmY*V%Wm5+5aiYT3Ic|2wXvi~i4>3=l0(rRTA<0T2ns%yF;RsSB-X0a=HDv=&Y^po z-I>|h&GrR*HepEfe#Ys96D={$SbN4LXfT2fn4jyhXF>s;a~emi4@WCh+p~a9#lQi$ zmJ)-G-pGb)>>BhC1GL;luC41j2TJ>(gYT3y=tC(gwd-dHnsx}Hd<>;!(H1(B{a%b1c#o|?RLxDxgxv9WVNtZhM_EM9Z|MkvJ<~P z(9JmEX|l_Td$v>mcjolEe&out=b_ke;ZX9L#v#r1;K@b4YS}FVa>DFONcoxk!5rQw z*<2FLJtI31zyFfW_qk3_Bra2`xT2Kn({307p>G7PMF>#`IDqn20XG48)9$%yjDc3_ zcNPH2SeG*WT(_+$%Ko;VM`SlQ?Cag<9}kb;K7IK}?n}<-Ybu#7sUF#joh$wW-{_F4 literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/6.0.16/linux-amd64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/6.0.16/linux-amd64/redis-server new file mode 100644 index 0000000000000000000000000000000000000000..a0d038ecdfd83bfd522ff49b1a1140ac59810f52 GIT binary patch literal 486 zcmY+AT}$jR5QcMWennu}yVE5fP0|z%w?lY&DB+*4<} z;J67oFvEp$PI{&k40M22xFA&v+_lD1?_f}rTG_P&4r7F$W!8a|!3f)e>7?R8an3XZ zFMw0cnN`w(Rt}Ul&I+a+3s!Yi-bvbVgYjmwA?{Fs9M2>rlaOP~E2%9^s*^2$`-4xW z8Qy2R*1RWM`F~{@KkG-1oIS*Bhi?WWdJMFU@dF)0?dXmvQ{7qLY9R*GFr`JyJ0*Sw zC`Ad%n&+1>J?}iq&3QRYK+ev{{pH=$!{fi(zc=XO{<`@Vt4!nL2&tgcxAcRQD1=f3wWdHyG literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/7.0.0/linux-amd64/redis-server b/syft/pkg/cataloger/binary/test-fixtures/classifiers/snippets/redis-server/7.0.0/linux-amd64/redis-server new file mode 100644 index 0000000000000000000000000000000000000000..8d2a3e7d3b8803c23d9c995d93fff13a7d0d0149 GIT binary patch literal 486 zcmZ9I!EW0y42FHJoD<~vaoC|$I q8OtW)$@lqUM$dmw@1GYxFS6P6*WL8n{H_lc1jS5Z;0YLrFnFVd-=zNV${$c5R;kB@I-Ff+z5<$BDJZySnw}a(R>RG&}}f zTJjW&HYN)OZ7?HTNup4Ic#$qQxS|9N z(f2*1AD4!e)dnkC3eFT`)Nn#UF|bzD7Aj?lHBJf!K$+rNfzYLvRiho3U`%Z*3tS-Q z13VS0gyXD{wq`&Z%2_Qar^*;DOa+YuBESfzYQmkSpske7LJ3e-KmjyEXt7w}dY=G4 zUK6JdyDo%ohckt4@}x^Po}!n%*@w Date: Fri, 5 Jan 2024 16:12:02 -0500 Subject: [PATCH 28/28] improve formating of fixture listing Signed-off-by: Alex Goodman --- syft/pkg/cataloger/binary/cataloger_test.go | 4 -- .../binary/test-fixtures/capture-snippet.sh | 2 + .../manager/internal/cli/commands/list.go | 61 ++++++++++++++----- .../manager/testutil/snippet_or_binary.go | 4 +- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 6b94dfcfc16..b21807b502f 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -315,7 +315,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) { }, }, { - // note: dynamic (non-snippet) test case logicalFixture: "redis-server/5.0.0/linux-amd64", expected: pkg.Package{ Name: "redis", @@ -327,7 +326,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) { }, }, { - // note: dynamic (non-snippet) test case logicalFixture: "redis-server/6.0.16/linux-amd64", expected: pkg.Package{ Name: "redis", @@ -339,7 +337,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) { }, }, { - // note: dynamic (non-snippet) test case logicalFixture: "redis-server/7.0.0/linux-amd64", expected: pkg.Package{ Name: "redis", @@ -351,7 +348,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) { }, }, { - // note: dynamic (non-snippet) test case logicalFixture: "redis-server/7.0.14/linux-amd64", expected: pkg.Package{ Name: "redis", diff --git a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh index 426e3b87d46..2c7fb063ea8 100755 --- a/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh +++ b/syft/pkg/cataloger/binary/test-fixtures/capture-snippet.sh @@ -51,6 +51,8 @@ while [[ $# -gt 0 ]]; do esac done +LENGTH=$(expr "$LENGTH" + "$PREFIX_LENGTH") + # check if binary file and pattern are provided if [ -z "$BINARY_FILE" ] || [ -z "$VERSION" ]; then usage diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go index 3e3b76aab2a..b968dda36e8 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/internal/cli/commands/list.go @@ -37,6 +37,10 @@ func runList(appConfig config.Application, showPaths bool) error { report := renderCatalogerListTable(material, showPaths) + report += "\n Legend:\n" + report += errorStyle.Render(" !! indicates there is no binary or snippet for a configured entry") + "\n" + report += warningStyle.Render(" ! indicates that the binary is managed but there is no snippet") + "\n" + fmt.Println(report) return nil @@ -74,33 +78,58 @@ func renderCatalogerListTable(material map[internal.LogicalEntryKey]internal.Ent } } - var invalid bool - if bin == "" && snippet == "" { - invalid = true + var state displayState + if snippet == "" { + if bin == "" { + state = displayStateError + } else { + state = displayStateWarning + } } t.AppendRow(table.Row{ - renderCell(k.OrgName, invalid), - renderCell(k.Version, invalid), - renderCell(displayPlatform(k.Platform), invalid), - renderCell(k.Filename, invalid), - renderCell(isConfigured, invalid), - renderCell(bin, invalid), - renderCell(snippet, invalid), + renderCell(k.OrgName, state, "!"), + renderCell(k.Version, state), + renderCell(displayPlatform(k.Platform), state), + renderCell(k.Filename, state), + renderCell(isConfigured, state), + renderCell(bin, state), + renderCell(snippet, state), }) } return t.Render() } -var errorStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("9")) -var stdStyle = lipgloss.NewStyle() +type displayState string + +const ( + displayStateError displayState = "error" + displayStateWarning displayState = "warning" +) + +var ( + errorStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("9")) + warningStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("11")) + stdStyle = lipgloss.NewStyle() +) -func renderCell(value string, invalid bool) string { - if invalid { - return errorStyle.Render(value) +func renderCell(value string, state displayState, hints ...string) string { + hint := strings.Join(hints, "") + var prefix string + switch state { + case displayStateError: + return errorStyle.Render(strings.Repeat(hint, 2) + value) + case displayStateWarning: + if hint != "" { + prefix = " " + } + return warningStyle.Render(hint + prefix + value) + } + if hint != "" { + prefix = " " } - return stdStyle.Render(value) + return stdStyle.Render(prefix + value) } func displayPlatform(platform string) string { diff --git a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go index 6d26d8202fc..d9d1e252e68 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go +++ b/syft/pkg/cataloger/binary/test-fixtures/manager/testutil/snippet_or_binary.go @@ -1,7 +1,7 @@ package testutil import ( - "errors" + "fmt" "os" "path/filepath" "testing" @@ -108,7 +108,7 @@ func validateSnippet(binaryPath, snippetPath string) error { } if expected != metadata.FileSha256 { - return errors.New("snippet shadows a binary with a different sha256") + return fmt.Errorf("snippet shadows a binary with a different sha256 (want %q got %q)", expected, metadata.FileSha256) } return nil