mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 14:17:20 +02:00
Compare commits
105
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78ad3d648f | ||
|
|
1b3e57c264 | ||
|
|
ad7edba2a5 | ||
|
|
e51d4bcda1 | ||
|
|
d8113b5f07 | ||
|
|
a35f64c971 | ||
|
|
5534c38d0f | ||
|
|
47fc909700 | ||
|
|
2e2a9377ea | ||
|
|
4ca79c7626 | ||
|
|
3743f5ae53 | ||
|
|
ebb9d4edb6 | ||
|
|
e2a9d891b2 | ||
|
|
5b09c154bb | ||
|
|
1b121ac3f4 | ||
|
|
f9e09aef19 | ||
|
|
d2ac672f8f | ||
|
|
5e3fde04a5 | ||
|
|
e214645394 | ||
|
|
5bd1cd5c13 | ||
|
|
fe4f17286f | ||
|
|
d7e58964ef | ||
|
|
59d54d6154 | ||
|
|
4d0dd02d5a | ||
|
|
356f7c92b4 | ||
|
|
6377465440 | ||
|
|
3ad91f2678 | ||
|
|
5ef83fdc79 | ||
|
|
06ff1a353a | ||
|
|
326ec57d4a | ||
|
|
48e5672a87 | ||
|
|
63171b55dd | ||
|
|
acc473fc30 | ||
|
|
a978966cad | ||
|
|
928511ea0f | ||
|
|
33b72ccbf8 | ||
|
|
2995c3c4fd | ||
|
|
170ac079d4 | ||
|
|
108a5dae9b | ||
|
|
cdf1e0bacb | ||
|
|
0c3b8ca4ed | ||
|
|
434b6ad506 | ||
|
|
3598cb4f8f | ||
|
|
8260bce057 | ||
|
|
578ac9cf2d | ||
|
|
a1b23bd57d | ||
|
|
65cadda486 | ||
|
|
a909e3cec9 | ||
|
|
8e62ff9831 | ||
|
|
96ee2db875 | ||
|
|
98b700e83c | ||
|
|
9803db2949 | ||
|
|
17ef243956 | ||
|
|
3ac7369068 | ||
|
|
4d4efa4963 | ||
|
|
a7da2270c7 | ||
|
|
6288530835 | ||
|
|
25d3c06962 | ||
|
|
79b71be0ee | ||
|
|
25ae7bf55f | ||
|
|
e72dec8e9e | ||
|
|
3398e72066 | ||
|
|
d26a5c4d30 | ||
|
|
84576b93e1 | ||
|
|
737c4e44c5 | ||
|
|
7444a9f976 | ||
|
|
8683cba081 | ||
|
|
c0f43e5e2d | ||
|
|
397cf210de | ||
|
|
bd0cb916df | ||
|
|
da31eed637 | ||
|
|
704155eb22 | ||
|
|
ce67927a98 | ||
|
|
bbd34f61fd | ||
|
|
c61f59e7b7 | ||
|
|
00d6269e3c | ||
|
|
0bc5971085 | ||
|
|
91d7a8a992 | ||
|
|
05fa8ba4e9 | ||
|
|
e813a427b9 | ||
|
|
0618b2cb35 | ||
|
|
fd3844853a | ||
|
|
b735106848 | ||
|
|
98de2e2f62 | ||
|
|
d7b9cc70b0 | ||
|
|
3023a5a7bc | ||
|
|
3da679066e | ||
|
|
b7a6d5e946 | ||
|
|
4fe50f4169 | ||
|
|
78d362f91a | ||
|
|
5a9b664fef | ||
|
|
6107e5e2ad | ||
|
|
fef0e54c0f | ||
|
|
4a98f9fbd3 | ||
|
|
db49c145f0 | ||
|
|
3ac7e43e3e | ||
|
|
216e211dc8 | ||
|
|
bbddac1f9d | ||
|
|
3893f80052 | ||
|
|
630e7153e6 | ||
|
|
28e9ee7106 | ||
|
|
43837f47f5 | ||
|
|
e880e6dcd6 | ||
|
|
6ae5b2904d | ||
|
|
377538e4a6 |
+5
-6
@@ -1,5 +1,4 @@
|
||||
tools:
|
||||
|
||||
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
|
||||
- name: binny
|
||||
version:
|
||||
@@ -27,7 +26,7 @@ tools:
|
||||
# used for linting
|
||||
- name: golangci-lint
|
||||
version:
|
||||
want: v1.55.2
|
||||
want: v1.56.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: golangci/golangci-lint
|
||||
@@ -43,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.2.2
|
||||
want: v2.2.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -59,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v1.23.0
|
||||
want: v1.24.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -104,7 +103,7 @@ tools:
|
||||
# used for running all local and CI tasks
|
||||
- name: task
|
||||
version:
|
||||
want: v3.34.1
|
||||
want: v3.35.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
@@ -112,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.42.1
|
||||
want: v2.45.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
enforce-v0: true # don't make breaking-change label bump major version before 1.0.
|
||||
enforce-v0: false
|
||||
title: ""
|
||||
|
||||
github:
|
||||
|
||||
@@ -176,7 +176,8 @@ def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
|
||||
|
||||
def list_json_schema_files() -> list[str]:
|
||||
# list files in "schema/json" directory matching the pattern of "schema-*.json"
|
||||
return sort_json_schema_files(list(glob.glob("schema/json/schema-*.json")))
|
||||
# special case: always ignore the "latest" schema file
|
||||
return sort_json_schema_files([f for f in glob.glob("schema/json/schema-*.json") if "latest" not in f])
|
||||
|
||||
|
||||
def run(command: str, **kwargs) -> subprocess.CompletedProcess:
|
||||
@@ -197,7 +198,7 @@ def sort_json_schema_files(files: list[str]) -> list[str]:
|
||||
# so that "schema/json/schema-1.2.1.json" comes before "schema/json/schema-1.12.1.json".
|
||||
versions = [get_semver(file) for file in files if file]
|
||||
|
||||
versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.')])
|
||||
versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.') if "." in s])
|
||||
|
||||
return [f"schema/json/schema-{version}.json" for version in versions]
|
||||
|
||||
|
||||
@@ -60,6 +60,11 @@ class Labeler(unittest.TestCase):
|
||||
expected_sorted_files = ["schema/json/schema-1.2.1.json", "schema/json/schema-1.12.1.json"]
|
||||
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
|
||||
|
||||
# ensure that "latest" doesn't cause a problem and is ultimately ignored
|
||||
files = ["schema/json/schema-1.12.1.json", "schema/json/schema-_bogus.json"]
|
||||
expected_sorted_files = ["schema/json/schema-_bogus.json", "schema/json/schema-1.12.1.json"]
|
||||
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
- name: Update PR benchmark results comment
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
header: benchmark
|
||||
|
||||
@@ -20,7 +20,7 @@ permissions:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04-4core-16gb
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 #v3.24.6
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 #v3.24.6
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 #v3.24.6
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Delete existing comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
hide: true
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- name: Add comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
path: ${{ env.CI_COMMENT_FILE }}
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
with:
|
||||
# we need to be able to get the commit for a tag
|
||||
fetch-depth: 0
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
echo -n "commit=$(git rev-parse HEAD)" | tee -a $GITHUB_OUTPUT
|
||||
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@75ed025ff3ad1d617862838b342b06d613a0ddf3 # v3.10.1
|
||||
uses: dawidd6/action-homebrew-bump-formula@baf2b60c51fc1f8453c884b0c61052668a71bd1d # v3.11.0
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW_TOKEN }}
|
||||
org: anchore
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Check if tag already exists
|
||||
# note: this will fail if the tag already exists
|
||||
@@ -94,8 +94,10 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -141,7 +143,7 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- uses: anchore/sbom-action@c6aed38a4323b393d05372c58a74c39ae8386d02 #v0.15.6
|
||||
- uses: anchore/sbom-action@9fece9e20048ca9590af301449208b2b8861333b #v0.15.9
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e #v6.0.2
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e #v6.0.2
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
|
||||
with:
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e #v6.0.2
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -31,37 +31,49 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore file executable test-fixture cache
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/file/cataloger/executable/test-fixtures/elf/bin
|
||||
key: ${{ runner.os }}-unit-file-executable-elf-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore file executable shared-info test-fixture cache
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/file/cataloger/executable/test-fixtures/shared-info/bin
|
||||
key: ${{ runner.os }}-unit-file-executable-shared-info-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/shared-info/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
|
||||
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore RPM test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/pkg/cataloger/redhat/test-fixtures/rpms
|
||||
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/redhat/test-fixtures/rpms.fingerprint' ) }}
|
||||
|
||||
- name: Restore go binary test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
|
||||
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
|
||||
|
||||
- name: Restore binary cataloger test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
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
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: syft/pkg/cataloger/kernel/test-fixtures/cache
|
||||
key: ${{ runner.os }}-unit-kernel-cache-${{ hashFiles( 'syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint' ) }}
|
||||
@@ -75,7 +87,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -84,7 +96,7 @@ jobs:
|
||||
run: make validate-cyclonedx-schema
|
||||
|
||||
- name: Restore integration test cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: ${{ github.workspace }}/cmd/syft/internal/test/integration/test-fixtures/cache
|
||||
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('/cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint') }}
|
||||
@@ -97,7 +109,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -110,7 +122,7 @@ jobs:
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
# we need to preserve the snapshot data itself as well as the task data that confirms if the
|
||||
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
|
||||
@@ -127,14 +139,14 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -152,7 +164,7 @@ jobs:
|
||||
|
||||
- name: Restore install.sh test image cache
|
||||
id: install-test-image-cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/install/cache
|
||||
key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
|
||||
@@ -175,7 +187,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -187,7 +199,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -202,7 +214,7 @@ jobs:
|
||||
|
||||
- name: Restore docker image cache for compare testing
|
||||
id: mac-compare-testing-cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: image.tar
|
||||
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
|
||||
@@ -220,20 +232,20 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore CLI test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/cli/test-fixtures/cache
|
||||
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
|
||||
@@ -54,6 +54,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Ruby (gem)
|
||||
- Rust (cargo.lock)
|
||||
- Swift (cocoapods, swift-package-manager)
|
||||
- Wordpress plugins
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -121,7 +122,8 @@ syft <image> --scope all-layers
|
||||
|
||||
### Supported sources
|
||||
|
||||
Syft can generate an SBOM from a variety of sources:
|
||||
Syft can generate an SBOM from a variety of sources including images, files, directories, and archives. Syft will attempt to
|
||||
determine the type of source based on provided input, for example:
|
||||
|
||||
```bash
|
||||
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
|
||||
@@ -134,26 +136,24 @@ syft path/to/image.sif
|
||||
syft path/to/dir
|
||||
```
|
||||
|
||||
Sources can be explicitly provided with a scheme:
|
||||
To explicitly specify the source behavior, use the `--from` flag. Allowable options are:
|
||||
|
||||
```
|
||||
docker:yourrepo/yourimage:tag use images from the Docker daemon
|
||||
podman:yourrepo/yourimage:tag use images from the Podman daemon
|
||||
containerd:yourrepo/yourimage:tag use images from the Containerd daemon
|
||||
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
file:path/to/yourproject/file read directly from a path on disk (any single file)
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
docker use images from the Docker daemon
|
||||
podman use images from the Podman daemon
|
||||
containerd use images from the Containerd daemon
|
||||
docker-archive use a tarball from disk for archives created from "docker save"
|
||||
oci-archive use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir read directly from a path on disk (any directory)
|
||||
file read directly from a path on disk (any single file)
|
||||
registry pull image directly from a registry (no container runtime required)
|
||||
```
|
||||
If a source is not provided and Syft identifies the input as a potential image reference, Syft will attempt to resolve it using:
|
||||
the Docker, Podman, and Containerd daemons followed by direct registry access, in that order.
|
||||
|
||||
If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
|
||||
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.
|
||||
|
||||
|
||||
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
|
||||
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](#configuration) for more details).
|
||||
|
||||
|
||||
### File selection
|
||||
@@ -228,12 +228,12 @@ syft <a directory> --select-catalogers "python"
|
||||
|
||||
Use the default set of catalogers and add a cataloger to the set:
|
||||
```bash
|
||||
syft ... --catalogers "+sbom-cataloger"
|
||||
syft ... --select-catalogers "+sbom-cataloger"
|
||||
```
|
||||
|
||||
Use the default set of catalogers but remove any catalogers that deal with RPMs:
|
||||
```bash
|
||||
syft ... --catalogers "-rpm"
|
||||
syft ... --select-catalogers "-rpm"
|
||||
```
|
||||
|
||||
Only scan with catalogers that:
|
||||
@@ -305,7 +305,7 @@ Where the `formats` available are:
|
||||
|
||||
## Using templates
|
||||
|
||||
Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template). Here's how it works:
|
||||
Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template) relative to the Syft JSON output. Here's how it works:
|
||||
|
||||
- Define your format as a Go template, and save this template as a file.
|
||||
|
||||
@@ -319,9 +319,9 @@ Syft lets you define custom output formats, using [Go templates](https://pkg.go.
|
||||
|
||||
Here's what the `csv.tmpl` file might look like:
|
||||
```gotemplate
|
||||
"Package","Version Installed","Found by"
|
||||
{{- range .Artifacts}}
|
||||
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
|
||||
"Package","Version Installed", "Found by"
|
||||
{{- range .artifacts}}
|
||||
"{{.name}}","{{.version}}","{{.foundBy}}"
|
||||
{{- end}}
|
||||
```
|
||||
|
||||
@@ -338,6 +338,9 @@ Syft also includes a vast array of utility templating functions from [sprig](htt
|
||||
|
||||
Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.
|
||||
|
||||
> [!NOTE]
|
||||
> If you have templates being used before Syft v0.102.0 that are no longer working. This is because templating keys were relative to the internal go structs before this version whereas now the keys are relative to the Syft JSON output. To get the legacy behavior back you can set the `format.template.legacy` option to `true` in your configuration.
|
||||
|
||||
## Multiple outputs
|
||||
|
||||
Syft can also output _multiple_ files in differing formats by appending
|
||||
@@ -548,7 +551,7 @@ catalogers:
|
||||
default-catalogers: []
|
||||
|
||||
# add, remove, and filter the catalogers to be used
|
||||
# SYFT_SELECT_CATALOGERS env var / --select-cataloger flag;
|
||||
# SYFT_SELECT_CATALOGERS env var / --select-catalogers flag;
|
||||
select-catalogers: []
|
||||
|
||||
# all format configuration
|
||||
@@ -688,6 +691,24 @@ golang:
|
||||
# SYFT_GOLANG_NOPROXY env var
|
||||
no-proxy: ""
|
||||
|
||||
# the go main module version discovered from binaries built with the go compiler will
|
||||
# always show (devel) as the version. Use these options to control heuristics to guess
|
||||
# a more accurate version from the binary.
|
||||
main-module-version:
|
||||
|
||||
# look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)
|
||||
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_LD_FLAGS env var
|
||||
from-ld-flags: true
|
||||
|
||||
# use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
|
||||
# (e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise.
|
||||
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_BUILD_SETTINGS env var
|
||||
from-build-settings: true
|
||||
|
||||
# search for semver-like strings in the binary contents.
|
||||
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_CONTENTS env var
|
||||
from-contents: true
|
||||
|
||||
java:
|
||||
maven-url: "https://repo1.maven.org/maven2"
|
||||
max-parent-recursive-depth: 5
|
||||
|
||||
+12
-5
@@ -266,6 +266,8 @@ tasks:
|
||||
desc: Generate test fixture fingerprints
|
||||
generates:
|
||||
- cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint
|
||||
- syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint
|
||||
- syft/file/cataloger/executable/test-fixtures/shared-info/cache.fingerprint
|
||||
- syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint
|
||||
- syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint
|
||||
- syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint
|
||||
@@ -274,17 +276,20 @@ tasks:
|
||||
- test/install/cache.fingerprint
|
||||
- test/cli/test-fixtures/cache.fingerprint
|
||||
cmds:
|
||||
# for EXECUTABLE unit test fixtures
|
||||
- "cd syft/file/cataloger/executable/test-fixtures/elf && make cache.fingerprint"
|
||||
- "cd syft/file/cataloger/executable/test-fixtures/shared-info && make cache.fingerprint"
|
||||
# for IMAGE integration test fixtures
|
||||
- "cd cmd/syft/internal/test/integration/test-fixtures && make cache.fingerprint"
|
||||
# for BINARY test fixtures
|
||||
# for BINARY unit test fixtures
|
||||
- "cd syft/pkg/cataloger/binary/test-fixtures && make cache.fingerprint"
|
||||
# for JAVA BUILD test fixtures
|
||||
# for JAVA BUILD unit test fixtures
|
||||
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cache.fingerprint"
|
||||
# for GO BINARY test fixtures
|
||||
# for GO BINARY unit test fixtures
|
||||
- "cd syft/pkg/cataloger/golang/test-fixtures/archs && make binaries.fingerprint"
|
||||
# for RPM test fixtures
|
||||
# for RPM unit test fixtures
|
||||
- "cd syft/pkg/cataloger/redhat/test-fixtures && make rpms.fingerprint"
|
||||
# for Kernel test fixtures
|
||||
# for Kernel unit test fixtures
|
||||
- "cd syft/pkg/cataloger/kernel/test-fixtures && make cache.fingerprint"
|
||||
# for INSTALL test fixtures
|
||||
- "cd test/install && make cache.fingerprint"
|
||||
@@ -294,6 +299,8 @@ tasks:
|
||||
fixtures:
|
||||
desc: Generate test fixtures
|
||||
cmds:
|
||||
- "cd syft/file/cataloger/executable/test-fixtures/elf && make"
|
||||
- "cd syft/file/cataloger/executable/test-fixtures/shared-info && make"
|
||||
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make"
|
||||
- "cd syft/pkg/cataloger/redhat/test-fixtures && make"
|
||||
- "cd syft/pkg/cataloger/binary/test-fixtures && make"
|
||||
|
||||
+2
-44
@@ -8,13 +8,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
handler "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
"github.com/anchore/syft/cmd/syft/internal"
|
||||
"github.com/anchore/syft/cmd/syft/internal/commands"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
// Application constructs the `syft packages` command and aliases the root command to `syft packages`.
|
||||
@@ -34,44 +29,7 @@ func Command(id clio.Identification) *cobra.Command {
|
||||
}
|
||||
|
||||
func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
|
||||
clioCfg := clio.NewSetupConfig(id).
|
||||
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
|
||||
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
|
||||
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
handler.New(handler.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
}, nil
|
||||
},
|
||||
).
|
||||
WithInitializers(
|
||||
func(state *clio.State) error {
|
||||
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
|
||||
// we can hoist them into the internal packages for global use.
|
||||
stereoscope.SetBus(state.Bus)
|
||||
bus.Set(state.Bus)
|
||||
|
||||
redact.Set(state.RedactStore)
|
||||
|
||||
log.Set(state.Logger)
|
||||
stereoscope.SetLogger(state.Logger)
|
||||
|
||||
return nil
|
||||
},
|
||||
).
|
||||
WithPostRuns(func(state *clio.State, err error) {
|
||||
stereoscope.Cleanup()
|
||||
})
|
||||
clioCfg := internal.AppClioSetupConfig(id, out)
|
||||
|
||||
app := clio.New(*clioCfg)
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
ui2 "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig {
|
||||
clioCfg := clio.NewSetupConfig(id).
|
||||
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
|
||||
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
|
||||
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
ui2.New(ui2.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
}, nil
|
||||
},
|
||||
).
|
||||
WithInitializers(
|
||||
func(state *clio.State) error {
|
||||
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
|
||||
// we can hoist them into the internal packages for global use.
|
||||
stereoscope.SetBus(state.Bus)
|
||||
bus.Set(state.Bus)
|
||||
|
||||
redact.Set(state.RedactStore)
|
||||
|
||||
log.Set(state.Logger)
|
||||
stereoscope.SetLogger(state.Logger)
|
||||
return nil
|
||||
},
|
||||
).
|
||||
WithPostRuns(func(_ *clio.State, _ error) {
|
||||
stereoscope.Cleanup()
|
||||
})
|
||||
return clioCfg
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -26,7 +27,6 @@ import (
|
||||
"github.com/anchore/syft/syft/format/spdxtagvalue"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,7 +42,7 @@ type attestOptions struct {
|
||||
options.Output `yaml:",inline" mapstructure:",squash"`
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
options.Catalog `yaml:",inline" mapstructure:",squash"`
|
||||
options.Attest `yaml:",inline" mapstructure:",squash"`
|
||||
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
|
||||
}
|
||||
|
||||
func Attest(app clio.Application) *cobra.Command {
|
||||
@@ -247,7 +247,11 @@ func predicateType(outputName string) string {
|
||||
}
|
||||
|
||||
func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
|
||||
src, err := getSource(opts, userInput, onlyContainerImages)
|
||||
if len(opts.From) > 1 || (len(opts.From) == 1 && opts.From[0] != stereoscope.RegistryTag) {
|
||||
return nil, fmt.Errorf("attest requires use of an OCI registry directly, one or more of the specified sources is unsupported: %v", opts.From)
|
||||
}
|
||||
|
||||
src, err := getSource(ctx, opts, userInput, stereoscope.RegistryTag)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -273,13 +277,6 @@ func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opt
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func onlyContainerImages(d *source.Detection) error {
|
||||
if !d.IsContainerImage() {
|
||||
return fmt.Errorf("attestations are only supported for oci images at this time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
|
||||
@@ -4,16 +4,21 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/clio/cliotestutils"
|
||||
"github.com/anchore/syft/cmd/syft/internal"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -267,3 +272,60 @@ func Test_buildSBOMForAttestation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_attestCLIWiring(t *testing.T) {
|
||||
id := clio.Identification{
|
||||
Name: "syft",
|
||||
Version: "testing",
|
||||
}
|
||||
cfg := internal.AppClioSetupConfig(id, io.Discard)
|
||||
tests := []struct {
|
||||
name string
|
||||
assertionFunc func(*testing.T, *cobra.Command, []string, ...any)
|
||||
wantOpts attestOptions
|
||||
args []string
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
name: "key flag is accepted",
|
||||
args: []string{"some-image:some-tag", "--key", "some-cosign-key.key"},
|
||||
assertionFunc: hasAttestOpts(options.Attest{Key: "some-cosign-key.key"}),
|
||||
},
|
||||
{
|
||||
name: "key password is read from env",
|
||||
args: []string{"some-image:some-tag", "--key", "cosign.key"},
|
||||
env: map[string]string{
|
||||
"SYFT_ATTEST_PASSWORD": "some-password",
|
||||
},
|
||||
assertionFunc: hasAttestOpts(options.Attest{
|
||||
Key: "cosign.key",
|
||||
Password: "some-password",
|
||||
}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != nil {
|
||||
for k, v := range tt.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
app := cliotestutils.NewApplication(t, cfg, tt.assertionFunc)
|
||||
cmd := Attest(app)
|
||||
cmd.SetArgs(tt.args)
|
||||
err := cmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func hasAttestOpts(wantOpts options.Attest) cliotestutils.AssertionFunc {
|
||||
return func(t *testing.T, _ *cobra.Command, _ []string, cfgs ...any) {
|
||||
assert.Equal(t, len(cfgs), 1)
|
||||
attestOpts, ok := cfgs[0].(*attestOptions)
|
||||
require.True(t, ok)
|
||||
if d := cmp.Diff(wantOpts, attestOpts.Attest); d != "" {
|
||||
t.Errorf("mismatched attest options (-want +got):\n%s", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func CatalogerList(app clio.Application) *cobra.Command {
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
Use: "list [OPTIONS]",
|
||||
Short: "List available catalogers",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runCatalogerList(opts)
|
||||
},
|
||||
}, opts)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
gologgerredact "github.com/anchore/go-logger/adapter/redact"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Initialize global state needed to test clio/cobra commands directly
|
||||
// Should be kept minimal.
|
||||
|
||||
// Initialize redact store once for all tests in the commands package
|
||||
// Redact store must be wired up here because syft will panic unless
|
||||
// a redact store is wired up exactly once
|
||||
redact.Set(gologgerredact.NewStore())
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func Convert(app clio.Application) *cobra.Command {
|
||||
}),
|
||||
Args: validateConvertArgs,
|
||||
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||
defer restoreStdout()
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
@@ -24,6 +26,7 @@ import (
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -162,14 +165,23 @@ func validateArgs(cmd *cobra.Command, args []string, error string) error {
|
||||
return cobra.MaximumNArgs(1)(cmd, args)
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
|
||||
writer, err := opts.SBOMWriter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := getSource(&opts.Catalog, userInput)
|
||||
sources := opts.From
|
||||
if len(sources) == 0 {
|
||||
// extract a scheme if it matches any provider tag; this is a holdover for compatibility, using the --from flag is recommended
|
||||
explicitSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceProviderTags()...)
|
||||
if explicitSource != "" {
|
||||
sources = append(sources, explicitSource)
|
||||
userInput = newUserInput
|
||||
}
|
||||
}
|
||||
|
||||
src, err := getSource(ctx, &opts.Catalog, userInput, sources...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -199,23 +211,21 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSource(opts *options.Catalog, userInput string, filters ...func(*source.Detection) error) (source.Source, error) {
|
||||
detection, err := source.Detect(
|
||||
userInput,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: opts.Source.Image.DefaultPullSource,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deteremine source: %w", err)
|
||||
}
|
||||
|
||||
for _, filter := range filters {
|
||||
if err := filter(detection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func getSource(ctx context.Context, opts *options.Catalog, userInput string, sources ...string) (source.Source, error) {
|
||||
cfg := syft.DefaultGetSourceConfig().
|
||||
WithRegistryOptions(opts.Registry.ToOptions()).
|
||||
WithAlias(source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
}).
|
||||
WithExcludeConfig(source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
}).
|
||||
WithBasePath(opts.Source.BasePath).
|
||||
WithSources(sources...).
|
||||
WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource)
|
||||
|
||||
var err error
|
||||
var platform *image.Platform
|
||||
|
||||
if opts.Platform != "" {
|
||||
@@ -223,29 +233,22 @@ func getSource(opts *options.Catalog, userInput string, filters ...func(*source.
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid platform: %w", err)
|
||||
}
|
||||
cfg = cfg.WithPlatform(platform)
|
||||
}
|
||||
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if opts.Source.File.Digests != nil {
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
|
||||
}
|
||||
cfg = cfg.WithDigestAlgorithms(hashers...)
|
||||
}
|
||||
|
||||
src, err := syft.GetSource(ctx, userInput, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
|
||||
return nil, fmt.Errorf("could not determine source: %w", err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(
|
||||
source.DetectionSourceConfig{
|
||||
Alias: source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
},
|
||||
RegistryOptions: opts.Registry.ToOptions(),
|
||||
Platform: platform,
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
},
|
||||
DigestAlgorithms: hashers,
|
||||
BasePath: opts.Source.BasePath,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if userInput == "power-user" {
|
||||
bus.Notify("Note: the 'power-user' command has been removed.")
|
||||
@@ -445,3 +448,7 @@ func getHintPhrase(expErr task.ErrInvalidExpression) string {
|
||||
func trimOperation(x string) string {
|
||||
return strings.TrimLeft(x, "+-")
|
||||
}
|
||||
|
||||
func allSourceProviderTags() []string {
|
||||
return collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All("", nil)...).Tags()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ var latestAppVersionURL = struct {
|
||||
}
|
||||
|
||||
func applicationUpdateCheck(id clio.Identification, check *options.UpdateCheck) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
return func(_ *cobra.Command, _ []string) error {
|
||||
if check.CheckForAppUpdate {
|
||||
checkForApplicationUpdate(id)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
)
|
||||
|
||||
var _ clio.FlagAdder = (*Attest)(nil)
|
||||
|
||||
type Attest struct {
|
||||
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
|
||||
Key secret `yaml:"key" json:"key" mapstructure:"key"`
|
||||
@@ -12,6 +14,6 @@ type Attest struct {
|
||||
|
||||
var _ clio.FlagAdder = (*Attest)(nil)
|
||||
|
||||
func (o Attest) AddFlags(flags clio.FlagSet) {
|
||||
func (o *Attest) AddFlags(flags clio.FlagSet) {
|
||||
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
@@ -45,6 +46,7 @@ type Catalog struct {
|
||||
|
||||
// configuration for the source (the subject being analyzed)
|
||||
Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
From []string `yaml:"from" json:"from" mapstructure:"from"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
@@ -61,6 +63,7 @@ func DefaultCatalog() Catalog {
|
||||
Scope: source.SquashedScope.String(),
|
||||
Package: defaultPackageConfig(),
|
||||
LinuxKernel: defaultLinuxKernelConfig(),
|
||||
Golang: defaultGolangConfig(),
|
||||
File: defaultFileConfig(),
|
||||
Relationships: defaultRelationshipsConfig(),
|
||||
Source: defaultSourceConfig(),
|
||||
@@ -111,6 +114,10 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
Globs: cfg.File.Content.Globs,
|
||||
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
|
||||
},
|
||||
Executable: executable.Config{
|
||||
MIMETypes: executable.DefaultConfig().MIMETypes,
|
||||
Globs: cfg.File.Executable.Globs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,13 +127,19 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
||||
}
|
||||
return pkgcataloging.Config{
|
||||
Binary: binary.DefaultCatalogerConfig(),
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Golang: golang.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy),
|
||||
WithNoProxy(cfg.Golang.NoProxy).
|
||||
WithMainModuleVersion(
|
||||
golang.DefaultMainModuleVersionConfig().
|
||||
WithFromContents(cfg.Golang.MainModuleVersion.FromContents).
|
||||
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
|
||||
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
||||
),
|
||||
JavaScript: javascript.DefaultCatalogerConfig().
|
||||
WithSearchRemoteLicenses(cfg.JavaScript.SearchRemoteLicenses).
|
||||
WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
|
||||
@@ -151,6 +164,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
|
||||
flags.StringVarP(&cfg.Scope, "scope", "s",
|
||||
fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))
|
||||
|
||||
flags.StringArrayVarP(&cfg.From, "from", "",
|
||||
"specify the source behavior to use (e.g. docker, registry, oci-dir, ...)")
|
||||
|
||||
flags.StringVarP(&cfg.Platform, "platform", "",
|
||||
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
|
||||
|
||||
@@ -208,6 +224,8 @@ func (cfg *Catalog) PostLoad() error {
|
||||
return out
|
||||
}
|
||||
|
||||
cfg.From = flatten(cfg.From)
|
||||
|
||||
cfg.Catalogers = flatten(cfg.Catalogers)
|
||||
cfg.DefaultCatalogers = flatten(cfg.DefaultCatalogers)
|
||||
cfg.SelectCatalogers = flatten(cfg.SelectCatalogers)
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type fileConfig struct {
|
||||
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
|
||||
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
|
||||
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Executable fileExecutable `yaml:"executable" json:"executable" mapstructure:"executable"`
|
||||
}
|
||||
|
||||
type fileMetadata struct {
|
||||
@@ -25,6 +26,10 @@ type fileContent struct {
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type fileExecutable struct {
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
func defaultFileConfig() fileConfig {
|
||||
return fileConfig{
|
||||
Metadata: fileMetadata{
|
||||
@@ -34,6 +39,9 @@ func defaultFileConfig() fileConfig {
|
||||
Content: fileContent{
|
||||
SkipFilesAboveSize: 250 * intFile.KB,
|
||||
},
|
||||
Executable: fileExecutable{
|
||||
Globs: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package options
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/spdxtagvalue"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
@@ -41,9 +42,10 @@ func (o Format) Encoders() ([]sbom.FormatEncoder, error) {
|
||||
return format.EncodersConfig{
|
||||
Template: o.Template.config(),
|
||||
SyftJSON: o.SyftJSON.config(),
|
||||
SPDXJSON: o.SPDXJSON.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
CyclonedxJSON: o.CyclonedxJSON.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
CyclonedxXML: o.CyclonedxXML.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
SPDXJSON: o.SPDXJSON.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
SPDXTagValue: spdxtagvalue.EncoderConfig{Version: format.AllVersions}, // we support multiple versions, not just a single version
|
||||
CyclonedxJSON: o.CyclonedxJSON.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
CyclonedxXML: o.CyclonedxXML.config(format.AllVersions), // we support multiple versions, not just a single version
|
||||
}.Encoders()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
)
|
||||
|
||||
type golangConfig struct {
|
||||
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
|
||||
}
|
||||
|
||||
type golangMainModuleVersionConfig struct {
|
||||
FromLDFlags bool `json:"from-ld-flags" yaml:"from-ld-flags" mapstructure:"from-ld-flags"`
|
||||
FromContents bool `json:"from-contents" yaml:"from-contents" mapstructure:"from-contents"`
|
||||
FromBuildSettings bool `json:"from-build-settings" yaml:"from-build-settings" mapstructure:"from-build-settings"`
|
||||
}
|
||||
|
||||
func defaultGolangConfig() golangConfig {
|
||||
def := golang.DefaultCatalogerConfig()
|
||||
return golangConfig{
|
||||
SearchLocalModCacheLicenses: def.SearchLocalModCacheLicenses,
|
||||
LocalModCacheDir: def.LocalModCacheDir,
|
||||
SearchRemoteLicenses: def.SearchRemoteLicenses,
|
||||
Proxy: strings.Join(def.Proxies, ","),
|
||||
NoProxy: strings.Join(def.NoProxy, ","),
|
||||
MainModuleVersion: golangMainModuleVersionConfig{
|
||||
FromLDFlags: def.MainModuleVersion.FromLDFlags,
|
||||
FromContents: def.MainModuleVersion.FromContents,
|
||||
FromBuildSettings: def.MainModuleVersion.FromBuildSettings,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
type sourceConfig struct {
|
||||
@@ -25,12 +27,13 @@ type imageSource struct {
|
||||
}
|
||||
|
||||
func defaultSourceConfig() sourceConfig {
|
||||
var digests []string
|
||||
for _, alg := range sourceproviders.DefaultConfig().DigestAlgorithms {
|
||||
digests = append(digests, alg.String())
|
||||
}
|
||||
return sourceConfig{
|
||||
File: fileSource{
|
||||
Digests: []string{"sha256"},
|
||||
},
|
||||
Image: imageSource{
|
||||
DefaultPullSource: "",
|
||||
Digests: digests,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,14 @@ var dirOnlyTestCases = []testCase{
|
||||
"unicode_util_compat": "0.7.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find ErLang OTP applications",
|
||||
pkgType: pkg.ErlangOTPPkg,
|
||||
pkgLanguage: pkg.Erlang,
|
||||
pkgInfo: map[string]string{
|
||||
"accept": "0.3.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find swift package manager packages",
|
||||
pkgType: pkg.SwiftPkg,
|
||||
@@ -444,4 +452,12 @@ var commonTestCases = []testCase{
|
||||
"glibc": "2.34-210",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find wordpress plugins",
|
||||
pkgType: pkg.WordpressPluginPkg,
|
||||
pkgLanguage: pkg.PHP,
|
||||
pkgInfo: map[string]string{
|
||||
"Akismet Anti-spam: Spam Protection": "5.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -22,15 +22,11 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
||||
tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
|
||||
|
||||
// get the source object for the image
|
||||
userInput := "docker-archive:" + tarPath
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(b, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(b, theSource.Close())
|
||||
})
|
||||
|
||||
// build the SBOM
|
||||
@@ -57,8 +53,8 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedLanguages.Remove(pkg.Swift.String())
|
||||
definedLanguages.Remove(pkg.CPP.String())
|
||||
definedLanguages.Remove(pkg.Haskell.String())
|
||||
definedLanguages.Remove(pkg.Erlang.String())
|
||||
definedLanguages.Remove(pkg.Elixir.String())
|
||||
definedLanguages.Remove(pkg.Erlang.String())
|
||||
|
||||
observedPkgs := strset.New()
|
||||
definedPkgs := strset.New()
|
||||
@@ -71,6 +67,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.GoModulePkg))
|
||||
definedPkgs.Remove(string(pkg.RustPkg))
|
||||
definedPkgs.Remove(string(pkg.DartPubPkg))
|
||||
definedPkgs.Remove(string(pkg.ErlangOTPPkg))
|
||||
definedPkgs.Remove(string(pkg.CocoapodsPkg))
|
||||
definedPkgs.Remove(string(pkg.ConanPkg))
|
||||
definedPkgs.Remove(string(pkg.HackagePkg))
|
||||
@@ -122,7 +119,6 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,7 +206,6 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -248,7 +243,6 @@ func TestPkgCoverageImage_HasEvidence(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
|
||||
assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
|
||||
for _, l := range a.Locations.ToSlice() {
|
||||
@@ -258,7 +252,6 @@ func TestPkgCoverageImage_HasEvidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -278,7 +271,6 @@ func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
|
||||
assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
|
||||
for _, l := range a.Locations.ToSlice() {
|
||||
@@ -288,7 +280,6 @@ func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGolangCompilerDetection(t *testing.T) {
|
||||
name: "syft can detect a single golang compiler given the golang base image",
|
||||
image: "image-golang-compiler",
|
||||
expectedCompilers: []string{"go1.18.10"},
|
||||
expectedCPE: []cpe.CPE{cpe.Must("cpe:2.3:a:golang:go:1.18.10:-:*:*:*:*:*:*")},
|
||||
expectedCPE: []cpe.CPE{cpe.Must("cpe:2.3:a:golang:go:1.18.10:-:*:*:*:*:*:*", cpe.GeneratedSource)},
|
||||
expectedPURL: []string{"pkg:golang/stdlib@1.18.10"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ var noAssertion = map[string]string{
|
||||
"/packages/TwilioNotifier.hpi:WEB-INF/lib/sdk-3.0.jar": "pkg:maven/sdk/sdk@3.0",
|
||||
|
||||
// syft generates incorrect purls
|
||||
"/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/akka-actor_2.13-2.6.6.jar": "pkg:maven/com.typesafe.akka/akka-actor_2.13@2.6.6",
|
||||
"/packages/akka-management-cluster-bootstrap_2.13-1.2.0.jar": "pkg:maven/com.lightbend.akka.management/akka-management-cluster-bootstrap_2.13@1.2.0",
|
||||
"/packages/hudson.war:WEB-INF/lib/asm-2.2.3.jar": "pkg:maven/asm/asm@2.2.3",
|
||||
@@ -366,10 +365,10 @@ var expectedPURLs = map[string]string{
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/org/yaml/snakeyaml/1.18/snakeyaml-1.18.jar": "pkg:maven/org.yaml/snakeyaml@1.18",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/readline.jar:jline:jline": "pkg:maven/jline/jline@2.11",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/readline.jar:rubygems:jruby-readline": "pkg:maven/rubygems/jruby-readline@1.2.0",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar": "pkg:maven/org.jruby/jruby-stdlib@9.1.15.0",
|
||||
"/packages/jsch-0.1.55.jar": "pkg:maven/com.jcraft/jsch@0.1.55",
|
||||
"/packages/junit-4.13.1.jar": "pkg:maven/junit/junit@4.13.1",
|
||||
// "/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar": "pkg:maven/org.jruby/jruby-stdlib@9.1.15.0",
|
||||
"/packages/jsch-0.1.55.jar": "pkg:maven/com.jcraft/jsch@0.1.55",
|
||||
"/packages/junit-4.13.1.jar": "pkg:maven/junit/junit@4.13.1",
|
||||
"/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/keycloak-core-22.0.2.jar": "pkg:maven/org.keycloak/keycloak-core@22.0.2",
|
||||
"/packages/log4j-1.2.16.jar": "pkg:maven/log4j/log4j@1.2.16",
|
||||
"/packages/log4j-core-2.17.0.jar": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0",
|
||||
|
||||
@@ -31,7 +31,9 @@ func Test_packageCatalogerExports(t *testing.T) {
|
||||
for pkg, expected := range expectAtLeast {
|
||||
actual, ok := exports[pkg]
|
||||
require.True(t, ok, pkg)
|
||||
require.True(t, expected.IsSubset(actual.Names()), pkg)
|
||||
if !assert.True(t, actual.Names().IsSubset(expected), pkg) {
|
||||
t.Logf("missing: %s", strset.SymmetricDifference(expected, actual.Names()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{application,accept,
|
||||
[{description,"Accept header(s) for Erlang/Elixir"},
|
||||
{vsn,"0.3.5"},
|
||||
{registered,[]},
|
||||
{applications,[kernel,stdlib]},
|
||||
{env,[]},
|
||||
{modules, ['accept_encoding_header','accept_header','accept_neg','accept_parser']},
|
||||
{maintainers,["Ilya Khaprov"]},
|
||||
{licenses,["MIT"]},
|
||||
{links,[{"Github","https://github.com/deadtrickster/accept"}]}]}.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Akismet
|
||||
*/
|
||||
/*
|
||||
Plugin Name:Akismet Anti-spam: Spam Protection
|
||||
Plugin URI: https://akismet.com/
|
||||
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
|
||||
Version: 5.3
|
||||
Requires at least: 5.8
|
||||
Requires PHP: 5.6.20
|
||||
Author: Automattic - Anti-spam Team
|
||||
Author URI: https://automattic.com/wordpress-plugins/
|
||||
License: GPLv2 or later
|
||||
Text Domain: akismet
|
||||
*/
|
||||
// rest of plugin's code ...
|
||||
@@ -34,17 +34,13 @@ func catalogFixtureImageWithConfig(t *testing.T, fixtureImageName string, cfg *s
|
||||
// get the fixture image tar file
|
||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
|
||||
// get the source to build an SBOM against
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(t, theSource.Close())
|
||||
})
|
||||
|
||||
s, err := syft.CreateSBOM(context.Background(), theSource, cfg)
|
||||
@@ -71,14 +67,10 @@ func catalogDirectoryWithConfig(t *testing.T, dir string, cfg *syft.CreateSBOMCo
|
||||
cfg.CatalogerSelection = cfg.CatalogerSelection.WithDefaults(pkgcataloging.DirectoryTag)
|
||||
|
||||
// get the source to build an sbom against
|
||||
userInput := "dir:" + dir
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), dir, syft.DefaultGetSourceConfig().WithSources("dir"))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(t, theSource.Close())
|
||||
})
|
||||
|
||||
// build the SBOM
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Syft API Examples
|
||||
|
||||
This directory contains examples of how to use the Syft API.
|
||||
|
||||
- `create_simple_sbom`: Create a simple SBOM from scratch
|
||||
- `create_custom_sbom`: Create an SBOM using as much custom configuration as possible, including a custom cataloger implementation
|
||||
- `decode_sbom`: Take an existing SBOM file (of arbitrary format) and decode it into a Syft SBOM object
|
||||
- `source_detection`: Shows how to detect what to catalog automatically from a user string (e.g. container image vs directory)
|
||||
- `source_from_image`: Construct a source from a only a container image
|
||||
|
||||
You can run any of these examples from this directory with:
|
||||
|
||||
```bash
|
||||
go run ./DIRECTORY_NAME
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
/*
|
||||
This is a contrived cataloger that attempts to capture useful APK files from the image as if it were a package.
|
||||
This isn't a real cataloger, but it is a good example of how to use API elements to create a custom cataloger.
|
||||
*/
|
||||
|
||||
var _ pkg.Cataloger = (*alpineConfigurationCataloger)(nil)
|
||||
|
||||
type alpineConfigurationCataloger struct {
|
||||
}
|
||||
|
||||
func newAlpineConfigurationCataloger() pkg.Cataloger {
|
||||
return alpineConfigurationCataloger{}
|
||||
}
|
||||
|
||||
func (m alpineConfigurationCataloger) Name() string {
|
||||
return "apk-configuration-cataloger"
|
||||
}
|
||||
|
||||
func (m alpineConfigurationCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
version, versionLocations, err := getVersion(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get alpine version: %w", err)
|
||||
}
|
||||
if len(versionLocations) == 0 {
|
||||
// this doesn't mean we should stop cataloging, just that we don't have a version to use, thus no package to raise up
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
metadata, metadataLocations, err := newAlpineConfiguration(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var locations []file.Location
|
||||
locations = append(locations, versionLocations...)
|
||||
locations = append(locations, metadataLocations...)
|
||||
|
||||
p := newPackage(version, *metadata, locations...)
|
||||
|
||||
return []pkg.Package{p}, nil, nil
|
||||
}
|
||||
|
||||
func newPackage(version string, metadata AlpineConfiguration, locations ...file.Location) pkg.Package {
|
||||
return pkg.Package{
|
||||
Name: "alpine-configuration",
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
Type: pkg.Type("system-configuration"), // you can make up your own package type here or use an existing one
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func newAlpineConfiguration(resolver file.Resolver) (*AlpineConfiguration, []file.Location, error) {
|
||||
var locations []file.Location
|
||||
|
||||
keys, keyLocations, err := getAPKKeys(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
locations = append(locations, keyLocations...)
|
||||
|
||||
return &AlpineConfiguration{
|
||||
APKKeys: keys,
|
||||
}, locations, nil
|
||||
|
||||
}
|
||||
|
||||
func getVersion(resolver file.Resolver) (string, []file.Location, error) {
|
||||
locations, err := resolver.FilesByPath("/etc/alpine-release")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to get alpine version: %w", err)
|
||||
}
|
||||
if len(locations) == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(locations[0])
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
|
||||
}
|
||||
|
||||
version, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
|
||||
}
|
||||
|
||||
return string(version), locations, nil
|
||||
}
|
||||
|
||||
func getAPKKeys(resolver file.Resolver) (map[string]string, []file.Location, error) {
|
||||
// name-to-content values
|
||||
keyContent := make(map[string]string)
|
||||
|
||||
locations, err := resolver.FilesByGlob("/etc/apk/keys/*.rsa.pub")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get apk keys: %w", err)
|
||||
}
|
||||
for _, location := range locations {
|
||||
basename := path.Base(location.RealPath)
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
|
||||
}
|
||||
keyContent[basename] = string(content)
|
||||
}
|
||||
return keyContent, locations, nil
|
||||
}
|
||||
|
||||
type AlpineConfiguration struct {
|
||||
APKKeys map[string]string `json:"apkKeys" yaml:"apkKeys"`
|
||||
// Add more data you want to capture as part of the package metadata here...
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// automagically get a source.Source for arbitrary string input
|
||||
src := getSource(imageReference())
|
||||
|
||||
// will catalog the given source and return a SBOM keeping in mind several configurable options
|
||||
sbom := getSBOM(src)
|
||||
|
||||
// show a simple package summary
|
||||
summarize(sbom)
|
||||
|
||||
// show the alpine-configuration cataloger results
|
||||
showAlpineConfiguration(sbom)
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
fmt.Println("detecting source type for input:", input, "...")
|
||||
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func getSBOM(src source.Source) sbom.SBOM {
|
||||
fmt.Println("creating SBOM...")
|
||||
|
||||
cfg := syft.DefaultCreateSBOMConfig().
|
||||
// run the catalogers in parallel (5 at a time concurrently max)
|
||||
WithParallelism(5).
|
||||
// bake a specific tool name and version into the SBOM
|
||||
WithTool("my-tool", "v1.0").
|
||||
// catalog all files with 3 digests
|
||||
WithFilesConfig(
|
||||
filecataloging.DefaultConfig().
|
||||
WithSelection(file.AllFilesSelection).
|
||||
WithHashers(
|
||||
crypto.MD5,
|
||||
crypto.SHA1,
|
||||
crypto.SHA256,
|
||||
),
|
||||
).
|
||||
// only use OS related catalogers that would have been used with the kind of
|
||||
// source type (container image or directory), but also add a specific python cataloger
|
||||
WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
WithSubSelections("os").
|
||||
WithAdditions("python-package-cataloger"),
|
||||
).
|
||||
// which relationships to include
|
||||
WithRelationshipsConfig(
|
||||
cataloging.RelationshipsConfig{
|
||||
PackageFileOwnership: true,
|
||||
PackageFileOwnershipOverlap: true,
|
||||
ExcludeBinaryPackagesWithFileOwnershipOverlap: true,
|
||||
},
|
||||
).
|
||||
// add your own cataloger to the mix
|
||||
WithCatalogers(
|
||||
pkgcataloging.NewAlwaysEnabledCatalogerReference(
|
||||
newAlpineConfigurationCataloger(),
|
||||
),
|
||||
)
|
||||
|
||||
s, err := syft.CreateSBOM(context.Background(), src, cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return *s
|
||||
}
|
||||
|
||||
func summarize(s sbom.SBOM) {
|
||||
fmt.Printf("Cataloged %d packages:\n", s.Artifacts.Packages.PackageCount())
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
fmt.Printf(" - %s@%s (%s)\n", p.Name, p.Version, p.Type)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showAlpineConfiguration(s sbom.SBOM) {
|
||||
pkgs := s.Artifacts.Packages.PackagesByName("alpine-configuration")
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Println("no alpine-configuration package found")
|
||||
return
|
||||
}
|
||||
|
||||
p := pkgs[0]
|
||||
|
||||
fmt.Printf("All 'alpine-configuration' packages: %s\n", p.Version)
|
||||
meta, err := yaml.Marshal(p.Metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(meta))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// automagically get a source.Source for arbitrary string input
|
||||
src := getSource(imageReference())
|
||||
|
||||
// catalog the given source and return a SBOM
|
||||
sbom := getSBOM(src)
|
||||
|
||||
// take the SBOM object and encode it into the syft-json representation
|
||||
bytes := formatSBOM(sbom)
|
||||
|
||||
// show the SBOM!
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func getSBOM(src source.Source) sbom.SBOM {
|
||||
s, err := syft.CreateSBOM(context.Background(), src, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return *s
|
||||
}
|
||||
|
||||
func formatSBOM(s sbom.SBOM) []byte {
|
||||
bytes, err := format.Encode(s, syftjson.NewFormatEncoder())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/format"
|
||||
)
|
||||
|
||||
//go:embed alpine.syft.json
|
||||
var sbomContents string
|
||||
|
||||
func main() {
|
||||
// decode the SBOM
|
||||
fmt.Println("decoding SBOM...")
|
||||
sbom, sbomFormat, formatVersion, err := format.Decode(sbomReader())
|
||||
if err != nil {
|
||||
fmt.Printf("failed to decode sbom: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("SBOM format: %s@%s\n", sbomFormat, formatVersion)
|
||||
|
||||
// print packages found...
|
||||
fmt.Println("\nPackages found:")
|
||||
for _, pkg := range sbom.Artifacts.Packages.Sorted() {
|
||||
fmt.Printf(" %s : %s@%s (%s)\n", pkg.ID(), pkg.Name, pkg.Version, pkg.Type)
|
||||
}
|
||||
|
||||
// print files found...
|
||||
fmt.Println("\nFiles found:")
|
||||
for c, f := range sbom.Artifacts.FileMetadata {
|
||||
fmt.Printf(" %s : %s\n", c.ID(), f.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func sbomReader() io.Reader {
|
||||
// read file from sys args (or use the default)
|
||||
var reader io.Reader
|
||||
if len(os.Args) < 2 {
|
||||
reader = strings.NewReader(sbomContents)
|
||||
} else {
|
||||
var err error
|
||||
reader, err = os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return reader
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// automagically get a source.Source for arbitrary string input
|
||||
src := getSource(imageReference())
|
||||
|
||||
// catalog the given source and return a SBOM
|
||||
// let's explicitly use catalogers that are:
|
||||
// - for installed software
|
||||
// - used in the directory scan
|
||||
sbom := getSBOM(src, pkgcataloging.InstalledTag, pkgcataloging.DirectoryTag)
|
||||
|
||||
// Show a basic catalogers and input configuration used
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(sbom.Descriptor.Configuration); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func getSBOM(src source.Source, defaultTags ...string) sbom.SBOM {
|
||||
cfg := syft.DefaultCreateSBOMConfig().
|
||||
WithCatalogerSelection(
|
||||
// here you can sub-select, add, remove catalogers from the default selection...
|
||||
// or replace the default selection entirely!
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
WithDefaults(defaultTags...),
|
||||
)
|
||||
|
||||
s, err := syft.CreateSBOM(context.Background(), src, cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return *s
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
/*
|
||||
This example demonstrates how to create a source object from a generic string input.
|
||||
|
||||
Example inputs:
|
||||
alpine:3.19 pull an image from the docker daemon, podman, or the registry (based on what's available)
|
||||
./my.tar interpret a local archive as an OCI archive, docker save archive, or raw file from disk to catalog
|
||||
docker:yourrepo/yourimage:tag explicitly use the Docker daemon
|
||||
podman:yourrepo/yourimage:tag explicitly use the Podman daemon
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
file:path/to/yourproject/file read directly from a path on disk (any single file)
|
||||
|
||||
*/
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
userInput := imageReference()
|
||||
|
||||
// parse the scheme against the known set of schemes
|
||||
schemeSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceTags()...)
|
||||
|
||||
// set up the GetSourceConfig
|
||||
getSourceCfg := syft.DefaultGetSourceConfig()
|
||||
if schemeSource != "" {
|
||||
getSourceCfg = getSourceCfg.WithSources(schemeSource)
|
||||
userInput = newUserInput
|
||||
}
|
||||
src, err := syft.GetSource(context.Background(), userInput, getSourceCfg)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Show a basic description of the source to the screen
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(src.Describe()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func allSourceTags() []string {
|
||||
return collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All("", nil)...).Tags()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image/oci"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
/*
|
||||
This shows how to create a source from an image reference. This is useful when you are programmatically always
|
||||
expecting to catalog a container image and always from the same source (e.g. docker daemon, podman, registry, etc).
|
||||
*/
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// using oci.Registry causes the lookup to always use the registry, there are several other "Source" options here
|
||||
img, err := stereoscope.GetImageFromSource(context.Background(), imageReference(), oci.Registry, stereoscope.WithPlatform("linux/amd64"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: imageReference(),
|
||||
})
|
||||
|
||||
// Show a basic description of the source to the screen
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(src.Describe()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
)
|
||||
|
||||
// This example demonstrates how to create an SBOM, pulling only from "registry", with error handling omitted
|
||||
func main() {
|
||||
image := "alpine:3.19"
|
||||
|
||||
src, _ := syft.GetSource(context.Background(), image, syft.DefaultGetSourceConfig().WithSources("registry"))
|
||||
|
||||
sbom, _ := syft.CreateSBOM(context.Background(), src, syft.DefaultCreateSBOMConfig())
|
||||
|
||||
_ = syftjson.NewFormatEncoder().Encode(os.Stdout, *sbom)
|
||||
}
|
||||
@@ -9,30 +9,31 @@ require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
|
||||
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/anchore/stereoscope v0.0.1
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/charmbracelet/bubbles v0.17.1
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/charmbracelet/lipgloss v0.10.0
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/docker/docker v25.0.1+incompatible
|
||||
github.com/docker/docker v25.0.4+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.2.0
|
||||
github.com/gkampitakis/go-snaps v0.5.0
|
||||
github.com/gkampitakis/go-snaps v0.5.2
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-test/deep v1.1.0
|
||||
@@ -47,16 +48,17 @@ require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.4
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
|
||||
github.com/knqyf263/go-rpmdb v0.1.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/sys/mountinfo v0.7.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/saferwall/pe v1.4.8
|
||||
github.com/saferwall/pe v1.5.2
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/sassoftware/go-rpmutils v0.3.0
|
||||
@@ -66,7 +68,7 @@ require (
|
||||
github.com/spdx/tools-golang v0.5.3
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
github.com/vifraa/gopom v1.0.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||
@@ -74,17 +76,16 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/mod v0.16.0
|
||||
golang.org/x/net v0.22.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.28.0
|
||||
modernc.org/sqlite v1.29.3
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
@@ -136,13 +137,13 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
@@ -160,16 +161,16 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/runc v1.1.12 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
@@ -180,7 +181,7 @@ require (
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
@@ -215,13 +216,13 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
@@ -229,15 +230,12 @@ require (
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.29.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.41.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -52,8 +52,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
@@ -93,10 +93,12 @@ github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58Pa
|
||||
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU=
|
||||
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 h1:u9XrEabKlGPsrmRvAER+kUKkwXiJfLyqGhmOTFsXjX4=
|
||||
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65/go.mod h1:8Jr7CjmwFVcBPtkJdTpaAGHimoGJGfbExypjzOu87Og=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
|
||||
@@ -107,10 +109,10 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.1 h1:OxF7PaxMltnAxjLnDMyka+SKRIQar/bBkDdavsnjyxM=
|
||||
github.com/anchore/stereoscope v0.0.1/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426 h1:agoiZchSf1Nnnos1azwIg5hk5Ao9TzZNBD9++AChGEg=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84 h1:/E74wU51M87fX5UWHubLZiENXbuAci+xtbSb+JFsIYg=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84/go.mod h1:evQiJMQG56Z7/L5uhA8kfhhjF6ESJUZzUH9ms6bQ2Co=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -151,19 +153,17 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
|
||||
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -182,7 +182,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
|
||||
@@ -206,7 +205,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE=
|
||||
@@ -225,15 +223,14 @@ github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qe
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.1+incompatible h1:k5TYd5rIVQRSqcTwCID+cyVA0yRg86+Pcrz1ls0/frA=
|
||||
github.com/docker/docker v25.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v25.0.4+incompatible h1:XITZTrq+52tZyZxUOtFIahUf3aH367FLxJzt9vZeAF8=
|
||||
github.com/docker/docker v25.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
@@ -270,7 +267,6 @@ github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
@@ -285,8 +281,8 @@ github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvp
|
||||
github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.0 h1:Qpk1+V1WJszsDvYMgrI15XbUvWqpsFvFxgaXKV6RU9Q=
|
||||
github.com/gkampitakis/go-snaps v0.5.0/go.mod h1:gX47VYsnvqHsjBrTrpaXfm1Dm1Xg+nREZPKRT8xifV8=
|
||||
github.com/gkampitakis/go-snaps v0.5.2 h1:ay/6f7WHwRkOgpBec9DjMLRBAApziJommZ21NkOOCwY=
|
||||
github.com/gkampitakis/go-snaps v0.5.2/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
@@ -318,7 +314,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -445,6 +440,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
@@ -487,8 +484,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y=
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@@ -500,14 +495,13 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b h1:boYyvL3tbUuKcMN029mpCl7oYYJ7yIXujLj+fiW4Alc=
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww=
|
||||
github.com/knqyf263/go-rpmdb v0.1.0 h1:pOgjtOGtW0B+ibY905hP3ETrYFmLZsHiReKsplcs+to=
|
||||
github.com/knqyf263/go-rpmdb v0.1.0/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -548,8 +542,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
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=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
@@ -577,9 +571,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
|
||||
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
|
||||
@@ -593,7 +586,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
@@ -603,6 +595,8 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -613,12 +607,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -667,8 +659,9 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
@@ -677,8 +670,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saferwall/pe v1.4.8 h1:ey/L8FGBMrJ1Xh+Rltj1MAFPZ4LOQYGJqNa5B1Na6B0=
|
||||
github.com/saferwall/pe v1.4.8/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s=
|
||||
github.com/saferwall/pe v1.5.2 h1:h5lLtLsyxGHQ9dN6cd8EfeLEBEo5gdqJpkuw4o4vTMY=
|
||||
github.com/saferwall/pe v1.5.2/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||
@@ -697,7 +690,6 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
@@ -740,8 +732,9 @@ github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -753,8 +746,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
@@ -762,7 +756,6 @@ github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM=
|
||||
github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y=
|
||||
github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ=
|
||||
github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
@@ -782,7 +775,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w=
|
||||
@@ -791,8 +783,6 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
|
||||
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8=
|
||||
@@ -873,8 +863,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -885,8 +875,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -915,8 +905,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -951,7 +941,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
@@ -965,8 +954,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -997,8 +986,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1012,7 +1001,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1020,7 +1008,6 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1062,12 +1049,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1082,15 +1067,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1166,8 +1151,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1360,34 +1345,22 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
|
||||
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
modernc.org/sqlite v1.29.3 h1:6L71d3zXVB8oubdVSuwiurNyYRetQ3It8l1FSwylwQ0=
|
||||
modernc.org/sqlite v1.29.3/go.mod h1:MjUIBKZ+tU/lqjNLbVAAMjsQPdWdA/ciwdhsT9kBwk8=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
+2
-1
@@ -694,6 +694,7 @@ main() (
|
||||
set +u
|
||||
if [ -z "${TEST_INSTALL_SH}" ]; then
|
||||
set -u
|
||||
exit $(main "$@")
|
||||
main "$@"
|
||||
exit $?
|
||||
fi
|
||||
set -u
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "14.0.0"
|
||||
JSONSchemaVersion = "16.0.6"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func NewDependencyRelationships(resolver file.Resolver, accessor sbomsync.Accessor) []artifact.Relationship {
|
||||
// TODO: consider library format (e.g. ELF, Mach-O, PE) for the meantime assume all binaries are homogeneous format
|
||||
|
||||
// start with building new file-to-file relationships for executables-to-executables
|
||||
// you need to make certain that they are unique, store in a map[id]map[id]relationship to avoid dupes.
|
||||
// before creating the new file-to-file relationship, check to see if there are packages that represent each
|
||||
// file. If there are, create a package-to-package, file-to-package, or package-to-file relationship as appropriate.
|
||||
|
||||
// 1 & 2... build an index of all shared libraries and their owning packages to search against
|
||||
index := newShareLibIndex(resolver, accessor)
|
||||
|
||||
// 3. craft package-to-package or package-to-file relationships that represent binary shared library dependencies
|
||||
// note: prefer package-to-package relationships over package-to-file relationships
|
||||
|
||||
relIndex := newRelationshipIndex()
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
// read all existing dependencyOf relationships
|
||||
for _, r := range s.Relationships {
|
||||
if r.Type != artifact.DependencyOfRelationship {
|
||||
continue
|
||||
}
|
||||
relIndex.track(r)
|
||||
}
|
||||
})
|
||||
|
||||
// find all new relationships to add...
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
for _, parentPkg := range s.Artifacts.Packages.Sorted(pkg.BinaryPkg) {
|
||||
for _, evidentLocation := range parentPkg.Locations.ToSlice() {
|
||||
if evidentLocation.Annotations[pkg.EvidenceAnnotationKey] != pkg.PrimaryEvidenceAnnotation {
|
||||
continue
|
||||
}
|
||||
|
||||
// find all libraries that this package depends on
|
||||
exec, ok := s.Artifacts.Executables[evidentLocation.Coordinates]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, libReference := range exec.ImportedLibraries {
|
||||
// TODO: is this always a basename? technically no, it could be a path...
|
||||
libBasename := path.Base(libReference)
|
||||
|
||||
pkgsThatOwnLib := index.owningLibraryPackage(libBasename)
|
||||
if pkgsThatOwnLib == nil {
|
||||
// create package-to-file relationship...
|
||||
// if there is more than one library for this given library name, then we will include
|
||||
// all of them as dependencies since we don't know the LD_LIBRARY_PATH order
|
||||
// TODO: add configuration for LD_LIBRARY_PATH order?
|
||||
for _, libCoord := range index.owningLibraryLocations(libBasename).ToSlice() {
|
||||
relIndex.add(
|
||||
artifact.Relationship{
|
||||
From: libCoord,
|
||||
To: parentPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// don't create a package-to-package relationship for this library... since we can't
|
||||
continue
|
||||
}
|
||||
|
||||
// create a package-to-package relationship between the binary package and the library package
|
||||
// if there is more than one library for this given library name, then we will include
|
||||
// all of them as dependencies since we don't know the LD_LIBRARY_PATH order
|
||||
for _, pkgThatOwnsLib := range pkgsThatOwnLib.Sorted() {
|
||||
relIndex.add(
|
||||
artifact.Relationship{
|
||||
From: pkgThatOwnsLib,
|
||||
To: parentPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// so far this handles the first order dependencies from the binary package. Odds are that the OS package manager
|
||||
// will have already created a package-to-package relationship for the lib packages to other lib packages.
|
||||
|
||||
return relIndex.newRelationships()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
|
||||
type relationshipIndex struct {
|
||||
typesByFromTo map[artifact.ID]map[artifact.ID]*strset.Set
|
||||
additional []artifact.Relationship
|
||||
}
|
||||
|
||||
func newRelationshipIndex(existing ...artifact.Relationship) *relationshipIndex {
|
||||
r := &relationshipIndex{
|
||||
typesByFromTo: make(map[artifact.ID]map[artifact.ID]*strset.Set),
|
||||
additional: make([]artifact.Relationship, 0),
|
||||
}
|
||||
for _, rel := range existing {
|
||||
r.track(rel)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// track this relationship as "exists" in the index (this is used to prevent duplicate relationships from being added).
|
||||
// returns true if the relationship is new to the index, false otherwise.
|
||||
func (i *relationshipIndex) track(r artifact.Relationship) bool {
|
||||
fromID := r.From.ID()
|
||||
if _, ok := i.typesByFromTo[fromID]; !ok {
|
||||
i.typesByFromTo[fromID] = make(map[artifact.ID]*strset.Set)
|
||||
}
|
||||
|
||||
toID := r.To.ID()
|
||||
if _, ok := i.typesByFromTo[fromID][toID]; !ok {
|
||||
i.typesByFromTo[fromID][toID] = strset.New()
|
||||
}
|
||||
|
||||
var exists bool
|
||||
if i.typesByFromTo[fromID][toID].Has(string(r.Type)) {
|
||||
exists = true
|
||||
}
|
||||
|
||||
i.typesByFromTo[fromID][toID].Add(string(r.Type))
|
||||
return exists
|
||||
}
|
||||
|
||||
// add a new relationship to the index, returning true if the relationship is new to the index, false otherwise (thus is a duplicate).
|
||||
func (i *relationshipIndex) add(r artifact.Relationship) bool {
|
||||
if !i.track(r) {
|
||||
i.additional = append(i.additional, r)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *relationshipIndex) newRelationships() []artifact.Relationship {
|
||||
return i.additional
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
type sharedLibraryIndex struct {
|
||||
libLocationsByBasename map[string]file.CoordinateSet
|
||||
allLibLocations file.CoordinateSet
|
||||
packagesByLibraryPath map[file.Coordinates]*pkg.Collection
|
||||
}
|
||||
|
||||
func newShareLibIndex(resolver file.Resolver, accessor sbomsync.Accessor) *sharedLibraryIndex {
|
||||
s := &sharedLibraryIndex{
|
||||
libLocationsByBasename: make(map[string]file.CoordinateSet),
|
||||
allLibLocations: file.NewCoordinateSet(),
|
||||
packagesByLibraryPath: make(map[file.Coordinates]*pkg.Collection),
|
||||
}
|
||||
|
||||
s.build(resolver, accessor)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *sharedLibraryIndex) build(resolver file.Resolver, accessor sbomsync.Accessor) {
|
||||
// 1. map out all locations that provide libraries (indexed by the basename)
|
||||
i.libLocationsByBasename, i.allLibLocations = locationsThatProvideLibraries(accessor)
|
||||
|
||||
// 2. for each library path, find all packages that claim ownership of the library
|
||||
i.packagesByLibraryPath = packagesWithLibraryOwnership(resolver, accessor, i.allLibLocations)
|
||||
}
|
||||
|
||||
func (i *sharedLibraryIndex) owningLibraryLocations(libraryBasename string) file.CoordinateSet {
|
||||
if set, ok := i.libLocationsByBasename[libraryBasename]; ok {
|
||||
return set
|
||||
}
|
||||
|
||||
return file.NewCoordinateSet()
|
||||
}
|
||||
|
||||
func (i *sharedLibraryIndex) owningLibraryPackage(libraryBasename string) *pkg.Collection {
|
||||
// find all packages that own a library by it's basename
|
||||
if set, ok := i.libLocationsByBasename[libraryBasename]; ok {
|
||||
for _, coord := range set.ToSlice() {
|
||||
if pkgSet, ok := i.packagesByLibraryPath[coord]; ok {
|
||||
return pkgSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func locationsThatProvideLibraries(accessor sbomsync.Accessor) (map[string]file.CoordinateSet, file.CoordinateSet) {
|
||||
// map out all locations that provide libraries (indexed by the basename)
|
||||
libLocationsByBasename := make(map[string]file.CoordinateSet)
|
||||
allLibLocations := file.NewCoordinateSet()
|
||||
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
for coord, f := range s.Artifacts.Executables {
|
||||
if !f.HasExports {
|
||||
continue
|
||||
}
|
||||
|
||||
basename := path.Base(coord.RealPath)
|
||||
set := libLocationsByBasename[basename]
|
||||
set.Add(coord)
|
||||
allLibLocations.Add(coord)
|
||||
libLocationsByBasename[basename] = set
|
||||
}
|
||||
})
|
||||
|
||||
return libLocationsByBasename, allLibLocations
|
||||
}
|
||||
func packagesWithLibraryOwnership(resolver file.Resolver, accessor sbomsync.Accessor, allLibLocations file.CoordinateSet) map[file.Coordinates]*pkg.Collection {
|
||||
// map out all packages that claim ownership of a library at a specific path
|
||||
packagesByLibraryPath := make(map[file.Coordinates]*pkg.Collection)
|
||||
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
fileOwner, ok := p.Metadata.(pkg.FileOwner)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pth := range fileOwner.OwnedFiles() {
|
||||
ownedLocation, err := resolver.FilesByPath(pth)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", pth).Trace("unable to find path for owned file")
|
||||
continue
|
||||
}
|
||||
|
||||
for _, loc := range ownedLocation {
|
||||
// if the location is a library, add the package to the set of packages that own the library
|
||||
if !allLibLocations.Contains(loc.Coordinates) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := packagesByLibraryPath[loc.Coordinates]; !ok {
|
||||
packagesByLibraryPath[loc.Coordinates] = pkg.NewCollection()
|
||||
}
|
||||
|
||||
// we have a library path, add the package to the set of packages that own the library
|
||||
packagesByLibraryPath[loc.Coordinates].Add(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return packagesByLibraryPath
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
package relationship
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/relationship/binary"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func Finalize(builder sbomsync.Builder, cfg cataloging.RelationshipsConfig, src artifact.Identifiable) {
|
||||
func Finalize(resolver file.Resolver, builder sbomsync.Builder, cfg cataloging.RelationshipsConfig, src artifact.Identifiable) {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
// remove ELF packages that are already represented by a non-ELF package
|
||||
// TODO (also, how should we update the TUI to reflect that we removed packages?)
|
||||
|
||||
// add relationships showing packages that are evident by a file which is owned by another package (package-to-package)
|
||||
if cfg.PackageFileOwnershipOverlap {
|
||||
byFileOwnershipOverlapWorker(accessor)
|
||||
@@ -21,6 +26,12 @@ func Finalize(builder sbomsync.Builder, cfg cataloging.RelationshipsConfig, src
|
||||
excludeBinariesByFileOwnershipOverlap(accessor)
|
||||
}
|
||||
|
||||
// add the new relationships for executables to the SBOM
|
||||
newBinaryRelationships := binary.NewDependencyRelationships(resolver, accessor)
|
||||
accessor.WriteToSBOM(func(s *sbom.SBOM) {
|
||||
s.Relationships = append(s.Relationships, newBinaryRelationships...)
|
||||
})
|
||||
|
||||
// add source "contains package" relationship (source-to-package)
|
||||
var sourceRelationships []artifact.Relationship
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2023-10-05 22:45:16.827263 -0400 EDT m=+0.138031521
|
||||
// This file was generated by robots at 2024-02-09 10:57:33.980847 -0500 EST m=+0.165923154
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.22"
|
||||
const Version = "3.23"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -13,6 +13,7 @@ var licenseIDs = map[string]string{
|
||||
"adobe2006": "Adobe-2006",
|
||||
"adobe2006.0": "Adobe-2006",
|
||||
"adobe2006.0.0": "Adobe-2006",
|
||||
"adobedisplaypostscript": "Adobe-Display-PostScript",
|
||||
"adobeglyph": "Adobe-Glyph",
|
||||
"adobeutopia": "Adobe-Utopia",
|
||||
"adsl": "ADSL",
|
||||
@@ -57,6 +58,7 @@ var licenseIDs = map[string]string{
|
||||
"aladdin": "Aladdin",
|
||||
"amdplpa": "AMDPLPA",
|
||||
"aml": "AML",
|
||||
"amlglslang": "AML-glslang",
|
||||
"ampas": "AMPAS",
|
||||
"antlrpd": "ANTLR-PD",
|
||||
"antlrpdfallback": "ANTLR-PD-fallback",
|
||||
@@ -108,6 +110,7 @@ var licenseIDs = map[string]string{
|
||||
"baekmuk": "Baekmuk",
|
||||
"bahyph": "Bahyph",
|
||||
"barr": "Barr",
|
||||
"bcryptsolardesigner": "bcrypt-Solar-Designer",
|
||||
"beerware": "Beerware",
|
||||
"bitstreamcharter": "Bitstream-Charter",
|
||||
"bitstreamvera": "Bitstream-Vera",
|
||||
@@ -122,6 +125,9 @@ var licenseIDs = map[string]string{
|
||||
"blueoak1.0.0": "BlueOak-1.0.0",
|
||||
"boehmgc": "Boehm-GC",
|
||||
"borceux": "Borceux",
|
||||
"briangladman2.0.0clause": "Brian-Gladman-2-Clause",
|
||||
"briangladman2.0clause": "Brian-Gladman-2-Clause",
|
||||
"briangladman2clause": "Brian-Gladman-2-Clause",
|
||||
"briangladman3.0.0clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3.0clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3clause": "Brian-Gladman-3-Clause",
|
||||
@@ -129,21 +135,25 @@ var licenseIDs = map[string]string{
|
||||
"bsd1.0clause": "BSD-1-Clause",
|
||||
"bsd1clause": "BSD-1-Clause",
|
||||
"bsd2.0.0clause": "BSD-2-Clause",
|
||||
"bsd2.0.0clausedarwin": "BSD-2-Clause-Darwin",
|
||||
"bsd2.0.0clausefreebsd": "BSD-2-Clause-Views",
|
||||
"bsd2.0.0clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2.0.0clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2.0.0clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd2.0clause": "BSD-2-Clause",
|
||||
"bsd2.0clausedarwin": "BSD-2-Clause-Darwin",
|
||||
"bsd2.0clausefreebsd": "BSD-2-Clause-Views",
|
||||
"bsd2.0clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2.0clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2.0clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd2clause": "BSD-2-Clause",
|
||||
"bsd2clausedarwin": "BSD-2-Clause-Darwin",
|
||||
"bsd2clausefreebsd": "BSD-2-Clause-Views",
|
||||
"bsd2clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd3.0.0clause": "BSD-3-Clause",
|
||||
"bsd3.0.0clauseacpica": "BSD-3-Clause-acpica",
|
||||
"bsd3.0.0clauseattribution": "BSD-3-Clause-Attribution",
|
||||
"bsd3.0.0clauseclear": "BSD-3-Clause-Clear",
|
||||
"bsd3.0.0clauseflex": "BSD-3-Clause-flex",
|
||||
@@ -157,6 +167,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd3.0.0clauseopenmpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd3.0.0clausesun": "BSD-3-Clause-Sun",
|
||||
"bsd3.0clause": "BSD-3-Clause",
|
||||
"bsd3.0clauseacpica": "BSD-3-Clause-acpica",
|
||||
"bsd3.0clauseattribution": "BSD-3-Clause-Attribution",
|
||||
"bsd3.0clauseclear": "BSD-3-Clause-Clear",
|
||||
"bsd3.0clauseflex": "BSD-3-Clause-flex",
|
||||
@@ -170,6 +181,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd3.0clauseopenmpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd3.0clausesun": "BSD-3-Clause-Sun",
|
||||
"bsd3clause": "BSD-3-Clause",
|
||||
"bsd3clauseacpica": "BSD-3-Clause-acpica",
|
||||
"bsd3clauseattribution": "BSD-3-Clause-Attribution",
|
||||
"bsd3clauseclear": "BSD-3-Clause-Clear",
|
||||
"bsd3clauseflex": "BSD-3-Clause-flex",
|
||||
@@ -201,8 +213,12 @@ var licenseIDs = map[string]string{
|
||||
"bsdattributionhpnddisclaimer": "BSD-Attribution-HPND-disclaimer",
|
||||
"bsdinfernonettverk": "BSD-Inferno-Nettverk",
|
||||
"bsdprotection": "BSD-Protection",
|
||||
"bsdsourcebeginningfile": "BSD-Source-beginning-file",
|
||||
"bsdsourcecode": "BSD-Source-Code",
|
||||
"bsdsystemics": "BSD-Systemics",
|
||||
"bsdsystemicsw3.0.0works": "BSD-Systemics-W3Works",
|
||||
"bsdsystemicsw3.0works": "BSD-Systemics-W3Works",
|
||||
"bsdsystemicsw3works": "BSD-Systemics-W3Works",
|
||||
"bsl1": "BSL-1.0",
|
||||
"bsl1.0": "BSL-1.0",
|
||||
"bsl1.0.0": "BSL-1.0",
|
||||
@@ -220,6 +236,7 @@ var licenseIDs = map[string]string{
|
||||
"cal1.0combinedworkexception": "CAL-1.0-Combined-Work-Exception",
|
||||
"cal1combinedworkexception": "CAL-1.0-Combined-Work-Exception",
|
||||
"caldera": "Caldera",
|
||||
"calderanopreamble": "Caldera-no-preamble",
|
||||
"catosl1": "CATOSL-1.1",
|
||||
"catosl1.1": "CATOSL-1.1",
|
||||
"catosl1.1.0": "CATOSL-1.1",
|
||||
@@ -241,16 +258,19 @@ var licenseIDs = map[string]string{
|
||||
"ccby3.0": "CC-BY-3.0",
|
||||
"ccby3.0.0": "CC-BY-3.0",
|
||||
"ccby3.0.0at": "CC-BY-3.0-AT",
|
||||
"ccby3.0.0au": "CC-BY-3.0-AU",
|
||||
"ccby3.0.0de": "CC-BY-3.0-DE",
|
||||
"ccby3.0.0igo": "CC-BY-3.0-IGO",
|
||||
"ccby3.0.0nl": "CC-BY-3.0-NL",
|
||||
"ccby3.0.0us": "CC-BY-3.0-US",
|
||||
"ccby3.0at": "CC-BY-3.0-AT",
|
||||
"ccby3.0au": "CC-BY-3.0-AU",
|
||||
"ccby3.0de": "CC-BY-3.0-DE",
|
||||
"ccby3.0igo": "CC-BY-3.0-IGO",
|
||||
"ccby3.0nl": "CC-BY-3.0-NL",
|
||||
"ccby3.0us": "CC-BY-3.0-US",
|
||||
"ccby3at": "CC-BY-3.0-AT",
|
||||
"ccby3au": "CC-BY-3.0-AU",
|
||||
"ccby3de": "CC-BY-3.0-DE",
|
||||
"ccby3igo": "CC-BY-3.0-IGO",
|
||||
"ccby3nl": "CC-BY-3.0-NL",
|
||||
@@ -420,6 +440,7 @@ var licenseIDs = map[string]string{
|
||||
"clartistic": "ClArtistic",
|
||||
"clips": "Clips",
|
||||
"cmumach": "CMU-Mach",
|
||||
"cmumachnodoc": "CMU-Mach-nodoc",
|
||||
"cnrijython": "CNRI-Jython",
|
||||
"cnripython": "CNRI-Python",
|
||||
"cnripythongplcompatible": "CNRI-Python-GPL-Compatible",
|
||||
@@ -456,6 +477,9 @@ var licenseIDs = map[string]string{
|
||||
"cuda1.0": "C-UDA-1.0",
|
||||
"cuda1.0.0": "C-UDA-1.0",
|
||||
"curl": "curl",
|
||||
"dec3.0.0clause": "DEC-3-Clause",
|
||||
"dec3.0clause": "DEC-3-Clause",
|
||||
"dec3clause": "DEC-3-Clause",
|
||||
"dfsl1": "D-FSL-1.0",
|
||||
"dfsl1.0": "D-FSL-1.0",
|
||||
"dfsl1.0.0": "D-FSL-1.0",
|
||||
@@ -471,6 +495,8 @@ var licenseIDs = map[string]string{
|
||||
"drl1": "DRL-1.0",
|
||||
"drl1.0": "DRL-1.0",
|
||||
"drl1.0.0": "DRL-1.0",
|
||||
"drl1.1": "DRL-1.1",
|
||||
"drl1.1.0": "DRL-1.1",
|
||||
"dsdp": "DSDP",
|
||||
"dtoa": "dtoa",
|
||||
"dvipdfm": "dvipdfm",
|
||||
@@ -526,12 +552,14 @@ var licenseIDs = map[string]string{
|
||||
"freebsddoc": "FreeBSD-DOC",
|
||||
"freeimage": "FreeImage",
|
||||
"fsfap": "FSFAP",
|
||||
"fsfapnowarrantydisclaimer": "FSFAP-no-warranty-disclaimer",
|
||||
"fsful": "FSFUL",
|
||||
"fsfullr": "FSFULLR",
|
||||
"fsfullrwd": "FSFULLRWD",
|
||||
"ftl": "FTL",
|
||||
"furuseth": "Furuseth",
|
||||
"fwlw": "fwlw",
|
||||
"gcrdocs": "GCR-docs",
|
||||
"gd": "GD",
|
||||
"gfdl1": "GFDL-1.1-only",
|
||||
"gfdl1+": "GFDL-1.1-or-later",
|
||||
@@ -672,7 +700,9 @@ var licenseIDs = map[string]string{
|
||||
"gsoap1.3.0b": "gSOAP-1.3b",
|
||||
"gsoap1.3b": "gSOAP-1.3b",
|
||||
"gsoap1b": "gSOAP-1.3b",
|
||||
"gtkbook": "gtkbook",
|
||||
"haskellreport": "HaskellReport",
|
||||
"hdparm": "hdparm",
|
||||
"hippocratic2": "Hippocratic-2.1",
|
||||
"hippocratic2.1": "Hippocratic-2.1",
|
||||
"hippocratic2.1.0": "Hippocratic-2.1",
|
||||
@@ -688,8 +718,13 @@ var licenseIDs = map[string]string{
|
||||
"hpnddocsell": "HPND-doc-sell",
|
||||
"hpndexportus": "HPND-export-US",
|
||||
"hpndexportusmodify": "HPND-export-US-modify",
|
||||
"hpndfenneberglivingston": "HPND-Fenneberg-Livingston",
|
||||
"hpndinriaimag": "HPND-INRIA-IMAG",
|
||||
"hpndkevlinhenney": "HPND-Kevlin-Henney",
|
||||
"hpndmarkuskuhn": "HPND-Markus-Kuhn",
|
||||
"hpndmitdisclaimer": "HPND-MIT-disclaimer",
|
||||
"hpndpbmplus": "HPND-Pbmplus",
|
||||
"hpndsellmitdisclaimerxserver": "HPND-sell-MIT-disclaimer-xserver",
|
||||
"hpndsellregexpr": "HPND-sell-regexpr",
|
||||
"hpndsellvariant": "HPND-sell-variant",
|
||||
"hpndsellvariantmitdisclaimer": "HPND-sell-variant-MIT-disclaimer",
|
||||
@@ -719,6 +754,7 @@ var licenseIDs = map[string]string{
|
||||
"ipl1.0": "IPL-1.0",
|
||||
"ipl1.0.0": "IPL-1.0",
|
||||
"isc": "ISC",
|
||||
"iscveillard": "ISC-Veillard",
|
||||
"jam": "Jam",
|
||||
"jasper2": "JasPer-2.0",
|
||||
"jasper2.0": "JasPer-2.0",
|
||||
@@ -802,6 +838,7 @@ var licenseIDs = map[string]string{
|
||||
"linuxmanpagescopyleftvar": "Linux-man-pages-copyleft-var",
|
||||
"linuxopenib": "Linux-OpenIB",
|
||||
"loop": "LOOP",
|
||||
"lpddocument": "LPD-document",
|
||||
"lpl1": "LPL-1.0",
|
||||
"lpl1.0": "LPL-1.0",
|
||||
"lpl1.0.0": "LPL-1.0",
|
||||
@@ -828,7 +865,14 @@ var licenseIDs = map[string]string{
|
||||
"lzmasdk9.22": "LZMA-SDK-9.22",
|
||||
"lzmasdk9.22.0": "LZMA-SDK-9.22",
|
||||
"lzmasdk9to9.20": "LZMA-SDK-9.11-to-9.20",
|
||||
"mackerras3.0.0clause": "Mackerras-3-Clause",
|
||||
"mackerras3.0.0clauseacknowledgment": "Mackerras-3-Clause-acknowledgment",
|
||||
"mackerras3.0clause": "Mackerras-3-Clause",
|
||||
"mackerras3.0clauseacknowledgment": "Mackerras-3-Clause-acknowledgment",
|
||||
"mackerras3clause": "Mackerras-3-Clause",
|
||||
"mackerras3clauseacknowledgment": "Mackerras-3-Clause-acknowledgment",
|
||||
"magaz": "magaz",
|
||||
"mailprio": "mailprio",
|
||||
"makeindex": "MakeIndex",
|
||||
"martinbirgmeier": "Martin-Birgmeier",
|
||||
"mcpheeslideshow": "McPhee-slideshow",
|
||||
@@ -1008,6 +1052,8 @@ var licenseIDs = map[string]string{
|
||||
"openpbs2.3": "OpenPBS-2.3",
|
||||
"openpbs2.3.0": "OpenPBS-2.3",
|
||||
"openssl": "OpenSSL",
|
||||
"opensslstandalone": "OpenSSL-standalone",
|
||||
"openvision": "OpenVision",
|
||||
"opl1": "OPL-1.0",
|
||||
"opl1.0": "OPL-1.0",
|
||||
"opl1.0.0": "OPL-1.0",
|
||||
@@ -1051,6 +1097,7 @@ var licenseIDs = map[string]string{
|
||||
"php3.0.0": "PHP-3.0",
|
||||
"php3.01": "PHP-3.01",
|
||||
"php3.01.0": "PHP-3.01",
|
||||
"pixar": "Pixar",
|
||||
"plexus": "Plexus",
|
||||
"pnmstitch": "pnmstitch",
|
||||
"polyformnoncommercial1": "PolyForm-Noncommercial-1.0.0",
|
||||
@@ -1077,6 +1124,7 @@ var licenseIDs = map[string]string{
|
||||
"qpl1.0.0inria2004": "QPL-1.0-INRIA-2004",
|
||||
"qpl1.0inria2004": "QPL-1.0-INRIA-2004",
|
||||
"qpl1inria2004": "QPL-1.0-INRIA-2004",
|
||||
"radvd": "radvd",
|
||||
"rdisc": "Rdisc",
|
||||
"rhecos1": "RHeCos-1.1",
|
||||
"rhecos1.1": "RHeCos-1.1",
|
||||
@@ -1094,6 +1142,9 @@ var licenseIDs = map[string]string{
|
||||
"ruby": "Ruby",
|
||||
"saxpath": "Saxpath",
|
||||
"saxpd": "SAX-PD",
|
||||
"saxpd2": "SAX-PD-2.0",
|
||||
"saxpd2.0": "SAX-PD-2.0",
|
||||
"saxpd2.0.0": "SAX-PD-2.0",
|
||||
"scea": "SCEA",
|
||||
"schemereport": "SchemeReport",
|
||||
"sendmail": "Sendmail",
|
||||
@@ -1129,6 +1180,7 @@ var licenseIDs = map[string]string{
|
||||
"smppl": "SMPPL",
|
||||
"snia": "SNIA",
|
||||
"snprintf": "snprintf",
|
||||
"softsurfer": "softSurfer",
|
||||
"soundex": "Soundex",
|
||||
"spencer86": "Spencer-86",
|
||||
"spencer86.0": "Spencer-86",
|
||||
@@ -1145,6 +1197,7 @@ var licenseIDs = map[string]string{
|
||||
"sshkeyscan": "ssh-keyscan",
|
||||
"sshopenssh": "SSH-OpenSSH",
|
||||
"sshshort": "SSH-short",
|
||||
"ssleaystandalone": "SSLeay-standalone",
|
||||
"sspl1": "SSPL-1.0",
|
||||
"sspl1.0": "SSPL-1.0",
|
||||
"sspl1.0.0": "SSPL-1.0",
|
||||
@@ -1152,6 +1205,7 @@ var licenseIDs = map[string]string{
|
||||
"sugarcrm1": "SugarCRM-1.1.3",
|
||||
"sugarcrm1.1": "SugarCRM-1.1.3",
|
||||
"sugarcrm1.1.3": "SugarCRM-1.1.3",
|
||||
"sunppp": "Sun-PPP",
|
||||
"sunpro": "SunPro",
|
||||
"swl": "SWL",
|
||||
"swrule": "swrule",
|
||||
@@ -1162,6 +1216,9 @@ var licenseIDs = map[string]string{
|
||||
"tcl": "TCL",
|
||||
"tcpwrappers": "TCP-wrappers",
|
||||
"termreadkey": "TermReadKey",
|
||||
"tgppl1": "TGPPL-1.0",
|
||||
"tgppl1.0": "TGPPL-1.0",
|
||||
"tgppl1.0.0": "TGPPL-1.0",
|
||||
"tmate": "TMate",
|
||||
"torque1": "TORQUE-1.1",
|
||||
"torque1.1": "TORQUE-1.1",
|
||||
@@ -1184,6 +1241,10 @@ var licenseIDs = map[string]string{
|
||||
"ucl1.0": "UCL-1.0",
|
||||
"ucl1.0.0": "UCL-1.0",
|
||||
"ulem": "ulem",
|
||||
"umichmerit": "UMich-Merit",
|
||||
"unicode3": "Unicode-3.0",
|
||||
"unicode3.0": "Unicode-3.0",
|
||||
"unicode3.0.0": "Unicode-3.0",
|
||||
"unicodedfs2015": "Unicode-DFS-2015",
|
||||
"unicodedfs2015.0": "Unicode-DFS-2015",
|
||||
"unicodedfs2015.0.0": "Unicode-DFS-2015",
|
||||
@@ -1236,6 +1297,7 @@ var licenseIDs = map[string]string{
|
||||
"xfree861.1": "XFree86-1.1",
|
||||
"xfree861.1.0": "XFree86-1.1",
|
||||
"xinetd": "xinetd",
|
||||
"xkeyboardconfigzinoviev": "xkeyboard-config-Zinoviev",
|
||||
"xlock": "xlock",
|
||||
"xnet": "Xnet",
|
||||
"xpp": "xpp",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// TODO: add tui element here?
|
||||
|
||||
func NewEnvironmentTask() Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
fn := func(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
release := linux.IdentifyRelease(resolver)
|
||||
if release != nil {
|
||||
builder.SetLinuxDistribution(*release)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filedigest"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filemetadata"
|
||||
@@ -100,6 +101,31 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
return NewTask("file-content-cataloger", fn)
|
||||
}
|
||||
|
||||
func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config) Task {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
}
|
||||
|
||||
cat := executable.NewCataloger(cfg)
|
||||
|
||||
fn := func(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.Executables = result
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn)
|
||||
}
|
||||
|
||||
// TODO: this should be replaced with a fix that allows passing a coordinate or location iterator to the cataloger
|
||||
// Today internal to both cataloger this functions differently: a slice of coordinates vs a channel of locations
|
||||
func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accessor) ([]file.Coordinates, bool) {
|
||||
|
||||
@@ -113,7 +113,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
// we might have binary classified CPE already with the package so we want to append here
|
||||
dictionaryCPE, ok := cpe.DictionaryFind(p)
|
||||
if ok {
|
||||
log.Tracef("used CPE dictionary to find CPE for %s package %q: %s", p.Type, p.Name, dictionaryCPE.BindToFmtString())
|
||||
log.Tracef("used CPE dictionary to find CPE for %s package %q: %s", p.Type, p.Name, dictionaryCPE.Attributes.BindToFmtString())
|
||||
p.CPEs = append(p.CPEs, dictionaryCPE)
|
||||
} else {
|
||||
p.CPEs = append(p.CPEs, cpe.Generate(p)...)
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
||||
sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/swift"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
@@ -57,6 +58,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
|
||||
newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"),
|
||||
newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"),
|
||||
newSimplePackageTaskFactory(haskell.NewHackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "haskell", "hackage", "cabal"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
@@ -111,10 +113,11 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
// other package catalogers ///////////////////////////////////////////////////////////////////////////
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return binary.NewCataloger(cfg.PackagesConfig.Binary)
|
||||
return binary.NewClassifierCataloger(cfg.PackagesConfig.Binary)
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary",
|
||||
),
|
||||
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package"),
|
||||
newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newPackageTaskFactory(
|
||||
@@ -124,5 +127,6 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
|
||||
),
|
||||
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ func (s sourceIdentifierAdapter) ID() artifact.ID {
|
||||
}
|
||||
|
||||
func NewRelationshipsTask(cfg cataloging.RelationshipsConfig, src source.Description) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
fn := func(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
relationship.Finalize(
|
||||
resolver,
|
||||
builder,
|
||||
cfg,
|
||||
&sourceIdentifierAdapter{desc: src})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,15 @@ import (
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Selection file.Selection `yaml:"selection" json:"selection" mapstructure:"selection"`
|
||||
Hashers []crypto.Hash `yaml:"hashers" json:"hashers" mapstructure:"hashers"`
|
||||
Content filecontent.Config `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Selection file.Selection `yaml:"selection" json:"selection" mapstructure:"selection"`
|
||||
Hashers []crypto.Hash `yaml:"hashers" json:"hashers" mapstructure:"hashers"`
|
||||
Content filecontent.Config `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Executable executable.Config `yaml:"executable" json:"executable" mapstructure:"executable"`
|
||||
}
|
||||
|
||||
type configMarshaledForm struct {
|
||||
@@ -30,9 +32,10 @@ func DefaultConfig() Config {
|
||||
log.WithFields("error", err).Warn("unable to create file hashers")
|
||||
}
|
||||
return Config{
|
||||
Selection: file.FilesOwnedByPackageSelection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.DefaultConfig(),
|
||||
Selection: file.FilesOwnedByPackageSelection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.DefaultConfig(),
|
||||
Executable: executable.DefaultConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +75,12 @@ func (cfg Config) WithSelection(selection file.Selection) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg Config) WithHashers(hashers []crypto.Hash) Config {
|
||||
func (cfg Config) WithHashers(hashers ...crypto.Hash) Config {
|
||||
cfg.Hashers = intFile.NormalizeHashes(hashers)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg Config) WithContentConfig(content filecontent.Config) Config {
|
||||
cfg.Content = content
|
||||
return cfg
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Binary binary.CatalogerConfig `yaml:"binary" json:"binary" mapstructure:"binary"`
|
||||
Binary binary.ClassifierCatalogerConfig `yaml:"binary" json:"binary" mapstructure:"binary"`
|
||||
Golang golang.CatalogerConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
JavaArchive java.ArchiveCatalogerConfig `yaml:"java-archive" json:"java-archive" mapstructure:"java-archive"`
|
||||
JavaScript javascript.CatalogerConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"`
|
||||
@@ -20,7 +20,7 @@ type Config struct {
|
||||
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Binary: binary.DefaultCatalogerConfig(),
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Golang: golang.DefaultCatalogerConfig(),
|
||||
LinuxKernel: kernel.DefaultLinuxKernelCatalogerConfig(),
|
||||
Python: python.DefaultCatalogerConfig(),
|
||||
@@ -28,7 +28,7 @@ func DefaultConfig() Config {
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) WithBinaryConfig(cfg binary.CatalogerConfig) Config {
|
||||
func (c Config) WithBinaryConfig(cfg binary.ClassifierCatalogerConfig) Config {
|
||||
c.Binary = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cpe
|
||||
|
||||
import "sort"
|
||||
|
||||
type BySourceThenSpecificity []CPE
|
||||
|
||||
func (b BySourceThenSpecificity) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b BySourceThenSpecificity) Less(i, j int) bool {
|
||||
sourceOrder := map[Source]int{
|
||||
NVDDictionaryLookupSource: 1,
|
||||
DeclaredSource: 2,
|
||||
GeneratedSource: 3,
|
||||
}
|
||||
|
||||
getRank := func(source Source) int {
|
||||
if rank, exists := sourceOrder[source]; exists {
|
||||
return rank
|
||||
}
|
||||
return 4 // Sourced we don't know about can't be assigned special priority, so
|
||||
// are considered ties.
|
||||
}
|
||||
iSource := b[i].Source
|
||||
jSource := b[j].Source
|
||||
rankI, rankJ := getRank(iSource), getRank(jSource)
|
||||
if rankI != rankJ {
|
||||
return rankI < rankJ
|
||||
}
|
||||
|
||||
return isMoreSpecific(b[i].Attributes, b[j].Attributes)
|
||||
}
|
||||
|
||||
func (b BySourceThenSpecificity) Swap(i, j int) {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
|
||||
var _ sort.Interface = (*BySourceThenSpecificity)(nil)
|
||||
@@ -0,0 +1,74 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBySourceThenSpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
want []CPE
|
||||
}{
|
||||
{
|
||||
name: "empty case",
|
||||
},
|
||||
{
|
||||
name: "nvd before generated",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "declared before generated",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "most specific attributes of equal sources",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "most specific attributes of unknown sources",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", ""),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", "some-other-unknown-source"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", "some-unknown-source"),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", "some-other-unknown-source"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", ""),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", "some-unknown-source"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sort.Sort(BySourceThenSpecificity(tt.input))
|
||||
assert.Equal(t, tt.want, tt.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
+23
-16
@@ -6,15 +6,22 @@ import (
|
||||
|
||||
var _ sort.Interface = (*BySpecificity)(nil)
|
||||
|
||||
type BySpecificity []CPE
|
||||
type BySpecificity []Attributes
|
||||
|
||||
func (c BySpecificity) Len() int { return len(c) }
|
||||
|
||||
func (c BySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
func (c BySpecificity) Less(i, j int) bool {
|
||||
iScore := weightedCountForSpecifiedFields(c[i])
|
||||
jScore := weightedCountForSpecifiedFields(c[j])
|
||||
return isMoreSpecific(c[i], c[j])
|
||||
}
|
||||
|
||||
// Returns true if i is more specific than j, with some
|
||||
// tie breaking mechanisms to make sorting equally-specific cpe Attributes
|
||||
// deterministic.
|
||||
func isMoreSpecific(i, j Attributes) bool {
|
||||
iScore := weightedCountForSpecifiedFields(i)
|
||||
jScore := weightedCountForSpecifiedFields(j)
|
||||
|
||||
// check weighted sort first
|
||||
if iScore != jScore {
|
||||
@@ -22,28 +29,28 @@ func (c BySpecificity) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
// sort longer fields to top
|
||||
if countFieldLength(c[i]) != countFieldLength(c[j]) {
|
||||
return countFieldLength(c[i]) > countFieldLength(c[j])
|
||||
if countFieldLength(i) != countFieldLength(j) {
|
||||
return countFieldLength(i) > countFieldLength(j)
|
||||
}
|
||||
|
||||
// if score and length are equal then text sort
|
||||
// note that we are not using String from the syft pkg
|
||||
// as we are not encoding/decoding this CPE string so we don't
|
||||
// need the proper quoted version of the CPE.
|
||||
return c[i].BindToFmtString() < c[j].BindToFmtString()
|
||||
// as we are not encoding/decoding this Attributes string so we don't
|
||||
// need the proper quoted version of the Attributes.
|
||||
return i.BindToFmtString() < j.BindToFmtString()
|
||||
}
|
||||
|
||||
func countFieldLength(cpe CPE) int {
|
||||
func countFieldLength(cpe Attributes) int {
|
||||
return len(cpe.Part + cpe.Vendor + cpe.Product + cpe.Version + cpe.TargetSW)
|
||||
}
|
||||
|
||||
func weightedCountForSpecifiedFields(cpe CPE) int {
|
||||
checksForSpecifiedField := []func(cpe CPE) (bool, int){
|
||||
func(cpe CPE) (bool, int) { return cpe.Part != "", 2 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Vendor != "", 3 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Product != "", 4 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Version != "", 1 },
|
||||
func(cpe CPE) (bool, int) { return cpe.TargetSW != "", 1 },
|
||||
func weightedCountForSpecifiedFields(cpe Attributes) int {
|
||||
checksForSpecifiedField := []func(cpe Attributes) (bool, int){
|
||||
func(cpe Attributes) (bool, int) { return cpe.Part != "", 2 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Vendor != "", 3 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Product != "", 4 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Version != "", 1 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.TargetSW != "", 1 },
|
||||
}
|
||||
|
||||
weightedCount := 0
|
||||
|
||||
@@ -10,81 +10,81 @@ import (
|
||||
func Test_BySpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
expected []CPE
|
||||
input []Attributes
|
||||
expected []Attributes
|
||||
}{
|
||||
{
|
||||
name: "sort strictly by wfn *",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort strictly by field length",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length and specificity",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length, specificity, dash",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+69
-30
@@ -8,9 +8,29 @@ import (
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
)
|
||||
|
||||
// CPE contains the attributes of an NVD Attributes and a string
|
||||
// describing where Syft got the Attributes, e.g. generated by heuristics
|
||||
// vs looked up in the NVD Attributes dictionary
|
||||
type CPE struct {
|
||||
Attributes Attributes
|
||||
Source Source
|
||||
}
|
||||
|
||||
type Source string
|
||||
|
||||
func (c Source) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
const (
|
||||
GeneratedSource Source = "syft-generated"
|
||||
NVDDictionaryLookupSource Source = "nvd-cpe-dictionary"
|
||||
DeclaredSource Source = "declared"
|
||||
)
|
||||
|
||||
const Any = ""
|
||||
|
||||
type CPE struct {
|
||||
type Attributes struct {
|
||||
Part string
|
||||
Vendor string
|
||||
Product string
|
||||
@@ -24,19 +44,19 @@ type CPE struct {
|
||||
Language string
|
||||
}
|
||||
|
||||
func (c CPE) asAttributes() wfn.Attributes {
|
||||
func (c Attributes) asAttributes() wfn.Attributes {
|
||||
return wfn.Attributes(c)
|
||||
}
|
||||
|
||||
func fromAttributes(a wfn.Attributes) CPE {
|
||||
return CPE(a)
|
||||
func fromAttributes(a wfn.Attributes) Attributes {
|
||||
return Attributes(a)
|
||||
}
|
||||
|
||||
func (c CPE) BindToFmtString() string {
|
||||
func (c Attributes) BindToFmtString() string {
|
||||
return c.asAttributes().BindToFmtString()
|
||||
}
|
||||
|
||||
func NewWithAny() CPE {
|
||||
func NewWithAny() Attributes {
|
||||
return fromAttributes(*(wfn.NewAttributesWithAny()))
|
||||
}
|
||||
|
||||
@@ -46,36 +66,55 @@ const (
|
||||
|
||||
// This regex string is taken from
|
||||
// https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd which has the official cpe spec
|
||||
// This first part matches CPE urls and the second part matches binding strings
|
||||
// This first part matches Attributes urls and the second part matches binding strings
|
||||
const cpeRegexString = ((`^([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\._\-~%]*){0,6})`) +
|
||||
// Or match the CPE binding string
|
||||
// Or match the Attributes binding string
|
||||
// Note that we had to replace '`' with '\x60' to escape the backticks
|
||||
`|(cpe:2\.3:[aho\*\-](:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&'\(\)\+,/:;<=>@\[\]\^\x60\{\|}~]))+(\?*|\*?))|[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&'\(\)\+,/:;<=>@\[\]\^\x60\{\|}~]))+(\?*|\*?))|[\*\-])){4})$`)
|
||||
|
||||
var cpeRegex = regexp.MustCompile(cpeRegexString)
|
||||
|
||||
// New will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
|
||||
// characters is allowed, however, a more strict validation is done after this sanitization process.
|
||||
func New(cpeStr string) (CPE, error) {
|
||||
// get a CPE object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
|
||||
c, err := newWithoutValidation(cpeStr)
|
||||
func New(value string, source Source) (CPE, error) {
|
||||
attributes, err := NewAttributes(value)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("unable to parse CPE string: %w", err)
|
||||
}
|
||||
|
||||
// ensure that this CPE can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
return CPE{}, err
|
||||
}
|
||||
return CPE{
|
||||
Attributes: attributes,
|
||||
Source: source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// we don't return the sanitized string, as this is a concern for later when creating CPE strings. In fact, since
|
||||
// NewAttributes will parse a formatted Attributes string and return a Attributes object. Some input, such as the existence of whitespace
|
||||
// characters is allowed, however, a more strict validation is done after this sanitization process.
|
||||
func NewAttributes(cpeStr string) (Attributes, error) {
|
||||
// get a Attributes object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
|
||||
c, err := newWithoutValidation(cpeStr)
|
||||
if err != nil {
|
||||
return Attributes{}, fmt.Errorf("unable to parse Attributes string: %w", err)
|
||||
}
|
||||
|
||||
// ensure that this Attributes can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
return Attributes{}, err
|
||||
}
|
||||
|
||||
// we don't return the sanitized string, as this is a concern for later when creating Attributes strings. In fact, since
|
||||
// sanitization is lossy (whitespace is replaced, not escaped) it's important that the raw values are left as.
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Must returns a CPE or panics if the provided string is not valid
|
||||
func Must(cpeStr string) CPE {
|
||||
c, err := New(cpeStr)
|
||||
func Must(cpeStr string, source Source) CPE {
|
||||
c := MustAttributes(cpeStr)
|
||||
return CPE{
|
||||
Attributes: c,
|
||||
Source: source,
|
||||
}
|
||||
}
|
||||
|
||||
func MustAttributes(cpeStr string) Attributes {
|
||||
c, err := NewAttributes(cpeStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -83,22 +122,22 @@ func Must(cpeStr string) CPE {
|
||||
}
|
||||
|
||||
func ValidateString(cpeStr string) error {
|
||||
// We should filter out all CPEs that do not match the official CPE regex
|
||||
// The facebook nvdtools parser can sometimes incorrectly parse invalid CPE strings
|
||||
// We should filter out all CPEs that do not match the official Attributes regex
|
||||
// The facebook nvdtools parser can sometimes incorrectly parse invalid Attributes strings
|
||||
if !cpeRegex.MatchString(cpeStr) {
|
||||
return fmt.Errorf("failed to parse CPE=%q as it doesn't match the regex=%s", cpeStr, cpeRegexString)
|
||||
return fmt.Errorf("failed to parse Attributes=%q as it doesn't match the regex=%s", cpeStr, cpeRegexString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newWithoutValidation(cpeStr string) (CPE, error) {
|
||||
func newWithoutValidation(cpeStr string) (Attributes, error) {
|
||||
value, err := wfn.Parse(cpeStr)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("failed to parse CPE=%q: %w", cpeStr, err)
|
||||
return Attributes{}, fmt.Errorf("failed to parse Attributes=%q: %w", cpeStr, err)
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return CPE{}, fmt.Errorf("failed to parse CPE=%q", cpeStr)
|
||||
return Attributes{}, fmt.Errorf("failed to parse Attributes=%q", cpeStr)
|
||||
}
|
||||
|
||||
syftCPE := fromAttributes(*value)
|
||||
@@ -120,7 +159,7 @@ func newWithoutValidation(cpeStr string) (CPE, error) {
|
||||
}
|
||||
|
||||
func normalizeField(field string) string {
|
||||
// replace spaces with underscores (per section 5.3.2 of the CPE spec v 2.3)
|
||||
// replace spaces with underscores (per section 5.3.2 of the Attributes spec v 2.3)
|
||||
field = strings.ReplaceAll(field, " ", "_")
|
||||
|
||||
// keep dashes and forward slashes unescaped
|
||||
@@ -144,8 +183,8 @@ func stripSlashes(s string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c CPE) String() string {
|
||||
output := CPE{}
|
||||
func (c Attributes) String() string {
|
||||
output := Attributes{}
|
||||
output.Vendor = sanitize(c.Vendor)
|
||||
output.Product = sanitize(c.Product)
|
||||
output.Language = sanitize(c.Language)
|
||||
|
||||
+26
-26
@@ -12,38 +12,38 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
func Test_NewAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected CPE
|
||||
expected Attributes
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
input: `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
|
||||
expected: Must(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
|
||||
},
|
||||
{
|
||||
name: "dashes",
|
||||
input: `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
|
||||
expected: Must(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
|
||||
},
|
||||
{
|
||||
name: "URL escape characters",
|
||||
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
|
||||
expected: Must(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := New(test.input)
|
||||
actual, err := NewAttributes(test.input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error while creating CPE: %+v", err)
|
||||
t.Fatalf("got an error while creating Attributes: %+v", err)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(actual, test.expected); d != "" {
|
||||
t.Errorf("CPE mismatch (-want +got):\n%s", d)
|
||||
t.Errorf("Attributes mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
})
|
||||
@@ -82,9 +82,9 @@ func Test_normalizeCpeField(t *testing.T) {
|
||||
|
||||
func Test_CPEParser(t *testing.T) {
|
||||
var testCases []struct {
|
||||
CPEString string `json:"cpe-string"`
|
||||
CPEUrl string `json:"cpe-url"`
|
||||
WFN CPE `json:"wfn"`
|
||||
CPEString string `json:"cpe-string"`
|
||||
CPEUrl string `json:"cpe-url"`
|
||||
WFN Attributes `json:"wfn"`
|
||||
}
|
||||
out, err := os.ReadFile("test-fixtures/cpe-data.json")
|
||||
require.NoError(t, err)
|
||||
@@ -92,9 +92,9 @@ func Test_CPEParser(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.CPEString, func(t *testing.T) {
|
||||
c1, err := New(test.CPEString)
|
||||
c1, err := NewAttributes(test.CPEString)
|
||||
assert.NoError(t, err)
|
||||
c2, err := New(test.CPEUrl)
|
||||
c2, err := NewAttributes(test.CPEUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c1, c2)
|
||||
assert.Equal(t, c1, test.WFN)
|
||||
@@ -161,11 +161,11 @@ func Test_InvalidCPE(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c, err := New(test.in)
|
||||
c, err := NewAttributes(test.in)
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
if t.Failed() {
|
||||
t.Logf("got CPE: %q details: %+v", c, c)
|
||||
t.Logf("got Attributes: %q details: %+v", c, c)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,12 +179,12 @@ func Test_RoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cpe string
|
||||
parsedCPE CPE
|
||||
parsedCPE Attributes
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
cpe: "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "some-vendor",
|
||||
Product: "name",
|
||||
@@ -194,7 +194,7 @@ func Test_RoundTrip(t *testing.T) {
|
||||
{
|
||||
name: "escaped colon",
|
||||
cpe: "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "some-vendor",
|
||||
Product: "name",
|
||||
@@ -204,7 +204,7 @@ func Test_RoundTrip(t *testing.T) {
|
||||
{
|
||||
name: "escaped forward slash",
|
||||
cpe: "cpe:2.3:a:test\\/some-vendor:name:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "test/some-vendor",
|
||||
Product: "name",
|
||||
@@ -215,13 +215,13 @@ func Test_RoundTrip(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// CPE string must be preserved through a round trip
|
||||
assert.Equal(t, test.cpe, Must(test.cpe).String())
|
||||
// The parsed CPE must be the same after a round trip
|
||||
assert.Equal(t, Must(test.cpe), Must(Must(test.cpe).String()))
|
||||
// The test case parsed CPE must be the same after parsing the input string
|
||||
assert.Equal(t, test.parsedCPE, Must(test.cpe))
|
||||
// The test case parsed CPE must produce the same string as the input cpe
|
||||
// Attributes string must be preserved through a round trip
|
||||
assert.Equal(t, test.cpe, MustAttributes(test.cpe).String())
|
||||
// The parsed Attributes must be the same after a round trip
|
||||
assert.Equal(t, MustAttributes(test.cpe), MustAttributes(MustAttributes(test.cpe).String()))
|
||||
// The test case parsed Attributes must be the same after parsing the input string
|
||||
assert.Equal(t, test.parsedCPE, MustAttributes(test.cpe))
|
||||
// The test case parsed Attributes must produce the same string as the input cpe
|
||||
assert.Equal(t, test.parsedCPE.String(), test.cpe)
|
||||
})
|
||||
}
|
||||
|
||||
+17
-15
@@ -1,25 +1,27 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func Merge(a, b []CPE) (result []CPE) {
|
||||
aCPEs := make(map[string]CPE)
|
||||
|
||||
// keep all CPEs from a and create a quick string-based lookup
|
||||
for _, aCPE := range a {
|
||||
aCPEs[aCPE.BindToFmtString()] = aCPE
|
||||
result = append(result, aCPE)
|
||||
// Merge returns unique SourcedCPEs that are found in A or B
|
||||
// Two SourcedCPEs are identical if their source and normalized string are identical
|
||||
func Merge(a, b []CPE) []CPE {
|
||||
var result []CPE
|
||||
dedupe := make(map[string]CPE)
|
||||
key := func(scpe CPE) string {
|
||||
return fmt.Sprintf("%s:%s", scpe.Source.String(), scpe.Attributes.BindToFmtString())
|
||||
}
|
||||
|
||||
// keep all unique CPEs from b
|
||||
for _, bCPE := range b {
|
||||
if _, exists := aCPEs[bCPE.BindToFmtString()]; !exists {
|
||||
result = append(result, bCPE)
|
||||
}
|
||||
for _, s := range a {
|
||||
dedupe[key(s)] = s
|
||||
}
|
||||
|
||||
sort.Sort(BySpecificity(result))
|
||||
for _, s := range b {
|
||||
dedupe[key(s)] = s
|
||||
}
|
||||
for _, val := range dedupe {
|
||||
result = append(result, val)
|
||||
}
|
||||
sort.Sort(BySourceThenSpecificity(result))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -16,18 +16,20 @@ func Test_Merge(t *testing.T) {
|
||||
name: "merge, removing duplicates and ordered",
|
||||
input: [][]CPE{
|
||||
{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/task"
|
||||
@@ -44,9 +45,34 @@ func DefaultCreateSBOMConfig() *CreateSBOMConfig {
|
||||
Files: filecataloging.DefaultConfig(),
|
||||
Parallelism: 1,
|
||||
packageTaskFactories: task.DefaultPackageTaskFactories(),
|
||||
|
||||
// library consumers are free to override the tool values to fit their needs, however, we have some sane defaults
|
||||
// to ensure that SBOMs generated don't have missing tool metadata.
|
||||
ToolName: "syft",
|
||||
ToolVersion: syftVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
func syftVersion() string {
|
||||
// extract the syft version from the go module info from the current binary that is running. This is useful for
|
||||
// library consumers to at least encode the version of syft that was used to generate the SBOM. Note: we don't
|
||||
// use the version info from main because it's baked in with ldflags, which we don't control for library consumers.
|
||||
// This approach won't work in all cases though, such as when the binary is stripped of the buildinfo section.
|
||||
|
||||
buildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, d := range buildInfo.Deps {
|
||||
if d.Path == "github.com/anchore/syft" && d.Version != "(devel)" {
|
||||
return d.Version
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// WithTool allows for setting the specific name, version, and any additional configuration that is not captured
|
||||
// in the syft default API configuration. This could cover inputs for catalogers that were user-provided, thus,
|
||||
// is not visible to the syft API, but would be useful to see in the SBOM output.
|
||||
@@ -185,6 +211,9 @@ func (c *CreateSBOMConfig) fileTasks() []task.Task {
|
||||
if t := task.NewFileContentCatalogerTask(c.Files.Content); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
if t := task.NewExecutableCatalogerTask(c.Files.Selection, c.Files.Executable); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
|
||||
return tsks
|
||||
}
|
||||
@@ -316,9 +345,9 @@ func (c *CreateSBOMConfig) Create(ctx context.Context, src source.Source) (*sbom
|
||||
|
||||
func findDefaultTag(src source.Description) (string, error) {
|
||||
switch m := src.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
return pkgcataloging.ImageTag, nil
|
||||
case source.FileSourceMetadata, source.DirectorySourceMetadata:
|
||||
case source.FileMetadata, source.DirectoryMetadata:
|
||||
return pkgcataloging.DirectoryTag, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unable to determine default cataloger tag for source type=%T", m)
|
||||
|
||||
@@ -62,15 +62,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
}
|
||||
|
||||
imgSrc := source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{},
|
||||
Metadata: source.ImageMetadata{},
|
||||
}
|
||||
|
||||
dirSrc := source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
}
|
||||
|
||||
fileSrc := source.Description{
|
||||
Metadata: source.FileSourceMetadata{},
|
||||
Metadata: source.FileMetadata{},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -88,7 +88,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -106,7 +106,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -125,7 +125,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -139,11 +139,11 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
{
|
||||
name: "no file digest cataloger",
|
||||
src: imgSrc,
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers(nil)),
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers()),
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(false, true), // note: the digest cataloger is not included
|
||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -181,7 +181,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
// note: there is a single group of catalogers for pkgs and files
|
||||
append(
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true)...,
|
||||
fileCatalogerNames(true, true, true)...,
|
||||
),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
@@ -202,7 +202,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -222,7 +222,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -242,7 +242,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -263,7 +263,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -283,7 +283,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -367,11 +367,14 @@ func pkgCatalogerNamesWithTagOrName(t *testing.T, token string) []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func fileCatalogerNames(digest, metadata bool) []string {
|
||||
func fileCatalogerNames(digest, metadata, executable bool) []string {
|
||||
var names []string
|
||||
if digest {
|
||||
names = append(names, "file-digest-cataloger")
|
||||
}
|
||||
if executable {
|
||||
names = append(names, "file-executable-cataloger")
|
||||
}
|
||||
if metadata {
|
||||
names = append(names, "file-metadata-cataloger")
|
||||
}
|
||||
@@ -434,21 +437,21 @@ func Test_findDefaultTag(t *testing.T) {
|
||||
{
|
||||
name: "image",
|
||||
src: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{},
|
||||
Metadata: source.ImageMetadata{},
|
||||
},
|
||||
want: pkgcataloging.ImageTag,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
src: source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
src: source.Description{
|
||||
Metadata: source.FileSourceMetadata{},
|
||||
Metadata: source.FileMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag, // not a mistake...
|
||||
},
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
MIMETypes []string `json:"mime-types" yaml:"mime-types" mapstructure:"mime-types"`
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type Cataloger struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
m := mimetype.ExecutableMIMETypeSet.List()
|
||||
sort.Strings(m)
|
||||
return Config{
|
||||
MIMETypes: m,
|
||||
Globs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCataloger(cfg Config) *Cataloger {
|
||||
return &Cataloger{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
||||
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
||||
}
|
||||
|
||||
locs, err = filterByGlobs(locs, i.config.Globs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prog := catalogingProgress(int64(len(locs)))
|
||||
|
||||
results := make(map[file.Coordinates]file.Executable)
|
||||
for _, loc := range locs {
|
||||
prog.AtomicStage.Set(loc.Path())
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||
continue
|
||||
}
|
||||
|
||||
uReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
||||
continue
|
||||
}
|
||||
|
||||
exec, err := processExecutable(loc, uReader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
|
||||
}
|
||||
if exec != nil {
|
||||
prog.Increment()
|
||||
results[loc.Coordinates] = *exec
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("executable cataloger processed %d files", len(results))
|
||||
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
info := monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
Default: "Executables",
|
||||
},
|
||||
ParentID: monitor.TopLevelCatalogingTaskID,
|
||||
}
|
||||
|
||||
return bus.StartCatalogerTask(info, locations, "")
|
||||
}
|
||||
|
||||
func filterByGlobs(locs []file.Location, globs []string) ([]file.Location, error) {
|
||||
if len(globs) == 0 {
|
||||
return locs, nil
|
||||
}
|
||||
var filteredLocs []file.Location
|
||||
for _, loc := range locs {
|
||||
matches, err := locationMatchesGlob(loc, globs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if matches {
|
||||
filteredLocs = append(filteredLocs, loc)
|
||||
}
|
||||
}
|
||||
return filteredLocs, nil
|
||||
}
|
||||
|
||||
func locationMatchesGlob(loc file.Location, globs []string) (bool, error) {
|
||||
for _, glob := range globs {
|
||||
for _, path := range []string{loc.RealPath, loc.AccessPath} {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
matches, err := doublestar.Match(glob, path)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to match glob %q to path %q: %w", glob, path, err)
|
||||
}
|
||||
if matches {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err := findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
||||
}
|
||||
case file.PE:
|
||||
if err := findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
||||
}
|
||||
case file.MachO:
|
||||
if err := findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
||||
}
|
||||
}
|
||||
|
||||
// always allocate collections for presentation
|
||||
if data.ImportedLibraries == nil {
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
// read the first sector of the file
|
||||
buf := make([]byte, 512)
|
||||
n, err := reader.ReadAt(buf, 0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read first sector of file: %w", err)
|
||||
}
|
||||
if n < 512 {
|
||||
return "", fmt.Errorf("unable to read enough bytes to determine executable format")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isMacho(buf):
|
||||
return file.MachO, nil
|
||||
case isPE(buf):
|
||||
return file.PE, nil
|
||||
case isELF(buf):
|
||||
return file.ELF, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func isMacho(by []byte) bool {
|
||||
// sourced from https://github.com/gabriel-vasile/mimetype/blob/02af149c0dfd1444d9256fc33c2012bb3153e1d2/internal/magic/binary.go#L44
|
||||
|
||||
if classOrMachOFat(by) && by[7] < 20 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(by) < 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
be := binary.BigEndian.Uint32(by)
|
||||
le := binary.LittleEndian.Uint32(by)
|
||||
|
||||
return be == macho.Magic32 ||
|
||||
le == macho.Magic32 ||
|
||||
be == macho.Magic64 ||
|
||||
le == macho.Magic64
|
||||
}
|
||||
|
||||
// Java bytecode and Mach-O binaries share the same magic number.
|
||||
// More info here https://github.com/threatstack/libmagic/blob/master/magic/Magdir/cafebabe
|
||||
func classOrMachOFat(in []byte) bool {
|
||||
// sourced from https://github.com/gabriel-vasile/mimetype/blob/02af149c0dfd1444d9256fc33c2012bb3153e1d2/internal/magic/binary.go#L44
|
||||
|
||||
// There should be at least 8 bytes for both of them because the only way to
|
||||
// quickly distinguish them is by comparing byte at position 7
|
||||
if len(in) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.HasPrefix(in, []byte{0xCA, 0xFE, 0xBA, 0xBE})
|
||||
}
|
||||
|
||||
func isPE(by []byte) bool {
|
||||
return bytes.HasPrefix(by, []byte("MZ"))
|
||||
}
|
||||
|
||||
func isELF(by []byte) bool {
|
||||
return bytes.HasPrefix(by, []byte(elf.ELFMAG))
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error {
|
||||
f, err := elf.NewFile(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
|
||||
libs = nil
|
||||
}
|
||||
|
||||
if libs == nil {
|
||||
libs = []string{}
|
||||
}
|
||||
|
||||
data.ImportedLibraries = libs
|
||||
data.ELFSecurityFeatures = findELFSecurityFeatures(f)
|
||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||
data.HasExports = elfHasExports(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||
return &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: isElfSymbolTableStripped(f),
|
||||
StackCanary: checkElfStackCanary(f),
|
||||
NoExecutable: checkElfNXProtection(f),
|
||||
RelocationReadOnly: checkElfRelROProtection(f),
|
||||
PositionIndependentExecutable: isELFPIE(f),
|
||||
DynamicSharedObject: isELFDSO(f),
|
||||
LlvmSafeStack: checkLLVMSafeStack(f),
|
||||
LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f),
|
||||
ClangFortifySource: checkClangFortifySource(f),
|
||||
}
|
||||
}
|
||||
|
||||
func isElfSymbolTableStripped(file *elf.File) bool {
|
||||
return file.Section(".symtab") == nil
|
||||
}
|
||||
|
||||
func checkElfStackCanary(file *elf.File) *bool {
|
||||
return hasAnyDynamicSymbols(file, "__stack_chk_fail", "__stack_chk_guard")
|
||||
}
|
||||
|
||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||
dynSyms, err := file.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
nameSet := strset.New(symbolNames...)
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if nameSet.Has(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func checkElfNXProtection(file *elf.File) bool {
|
||||
// find the program headers until you find the GNU_STACK segment
|
||||
for _, prog := range file.Progs {
|
||||
if prog.Type == elf.PT_GNU_STACK {
|
||||
// check if the GNU_STACK segment is executable
|
||||
return prog.Flags&elf.PF_X == 0
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkElfRelROProtection(f *elf.File) file.RelocationReadOnly {
|
||||
// background on relro https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro
|
||||
hasRelro := false
|
||||
hasBindNow := hasBindNowDynTagOrFlag(f)
|
||||
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type == elf.PT_GNU_RELRO {
|
||||
hasRelro = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case hasRelro && hasBindNow:
|
||||
return file.RelocationReadOnlyFull
|
||||
case hasRelro:
|
||||
return file.RelocationReadOnlyPartial
|
||||
default:
|
||||
return file.RelocationReadOnlyNone
|
||||
}
|
||||
}
|
||||
|
||||
func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
||||
if hasElfDynTag(f, elf.DT_BIND_NOW) {
|
||||
// support older binaries...
|
||||
return true
|
||||
}
|
||||
|
||||
// "DT_BIND_NOW ... use has been superseded by the DF_BIND_NOW flag"
|
||||
// source: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||
return hasElfDynFlag(f, elf.DF_BIND_NOW)
|
||||
}
|
||||
|
||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynTag(f *elf.File, tag elf.DynTag) bool {
|
||||
// source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280
|
||||
|
||||
ds := f.SectionByType(elf.SHT_DYNAMIC)
|
||||
if ds == nil {
|
||||
return false
|
||||
}
|
||||
d, err := ds.Data()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for len(d) > 0 {
|
||||
var t elf.DynTag
|
||||
switch f.Class {
|
||||
case elf.ELFCLASS32:
|
||||
t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
|
||||
d = d[8:]
|
||||
case elf.ELFCLASS64:
|
||||
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
|
||||
d = d[16:]
|
||||
}
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isELFPIE(f *elf.File) bool {
|
||||
// being a shared object is not sufficient to be a PIE, the explicit flag must be set also
|
||||
return isELFDSO(f) && hasElfDynFlag1(f, elf.DF_1_PIE)
|
||||
}
|
||||
|
||||
func isELFDSO(f *elf.File) bool {
|
||||
return f.Type == elf.ET_DYN
|
||||
}
|
||||
|
||||
func checkLLVMSafeStack(file *elf.File) *bool {
|
||||
// looking for the presence of https://github.com/microsoft/compiler-rt/blob/30b3b8cb5c9a0854f2f40f187c6f6773561a35f2/lib/safestack/safestack.cc#L207
|
||||
return hasAnyDynamicSymbols(file, "__safestack_init")
|
||||
}
|
||||
|
||||
func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
||||
// look for any symbols that are functions and end with ".cfi"
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && strings.HasSuffix(sym.Name, ".cfi") {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func isFunction(sym elf.Symbol) bool {
|
||||
return elf.ST_TYPE(sym.Info) == elf.STT_FUNC
|
||||
}
|
||||
|
||||
var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
||||
|
||||
func checkClangFortifySource(file *elf.File) *bool {
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && fortifyPattern.MatchString(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func elfHasEntrypoint(f *elf.File) bool {
|
||||
// this is akin to
|
||||
// readelf -h ./path/to/bin | grep "Entry point address"
|
||||
return f.Entry > 0
|
||||
}
|
||||
|
||||
func elfHasExports(f *elf.File) bool {
|
||||
// this is akin to:
|
||||
// nm -D --defined-only ./path/to/bin | grep ' T \| W \| B '
|
||||
// where:
|
||||
// T - symbol in the text section
|
||||
// W - weak symbol that might be overwritten
|
||||
// B - variable located in the uninitialized data section
|
||||
// really anything that is not marked with 'U' (undefined) is considered an export.
|
||||
symbols, err := f.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns?
|
||||
return false
|
||||
}
|
||||
|
||||
for _, s := range symbols {
|
||||
// check if the section is SHN_UNDEF, which is the "U" output type from "nm -D" meaning that the symbol
|
||||
// is undefined, meaning it is not an export. Any entry that is not undefined is considered an export.
|
||||
if s.Section != elf.SHN_UNDEF {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func Test_findELFSecurityFeatures(t *testing.T) {
|
||||
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/elf", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want *file.ELFSecurityFeatures
|
||||
wantStripped bool
|
||||
}{
|
||||
{
|
||||
name: "detect canary",
|
||||
fixture: "bin/with_canary",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(true), // ! important !
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect nx",
|
||||
fixture: "bin/with_nx",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
NoExecutable: true, // ! important !
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect relro",
|
||||
fixture: "bin/with_relro",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyFull, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect partial relro",
|
||||
fixture: "bin/with_partial_relro",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect pie",
|
||||
fixture: "bin/with_pie",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
PositionIndependentExecutable: true, // ! important !
|
||||
DynamicSharedObject: true, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect dso",
|
||||
fixture: "bin/pie_false_positive.so",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
NoExecutable: true,
|
||||
PositionIndependentExecutable: false, // ! important !
|
||||
DynamicSharedObject: true, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect safestack",
|
||||
fixture: "bin/with_safestack",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(true), // ! important !
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect cfi",
|
||||
fixture: "bin/with_cfi",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(true), // ! important !
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect fortify",
|
||||
fixture: "bin/with_fortify",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(true), // ! important !
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
|
||||
got := findELFSecurityFeatures(f)
|
||||
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("findELFSecurityFeatures() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfHasEntrypoint(t *testing.T) {
|
||||
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/libhello.so",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello_linux",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, elfHasEntrypoint(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfHasExports(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/libhello.so",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello_linux",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, elfHasExports(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/macho"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
// source http://www.cilinder.be/docs/next/NeXTStep/3.3/nd/DevTools/14_MachO/MachO.htmld/index.html
|
||||
const (
|
||||
machoNPExt uint8 = 0x10 /* N_PEXT: private external symbol bit */
|
||||
machoNExt uint8 = 0x01 /* N_EXT: external symbol bit, set for external symbols */
|
||||
// > #define LC_REQ_DYLD 0x80000000
|
||||
// > #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
|
||||
lcMain = 0x28 | 0x80000000
|
||||
)
|
||||
|
||||
func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error {
|
||||
// TODO: support security features
|
||||
|
||||
// TODO: support multi-architecture binaries
|
||||
f, err := macho.NewFile(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.ImportedLibraries = libs
|
||||
data.HasEntrypoint = machoHasEntrypoint(f)
|
||||
data.HasExports = machoHasExports(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func machoHasEntrypoint(f *macho.File) bool {
|
||||
// derived from struct entry_point_command found from which explicitly calls out LC_MAIN:
|
||||
// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
|
||||
// we need to look for both LC_MAIN and LC_UNIXTHREAD commands to determine if the file is an executable
|
||||
//
|
||||
// this is akin to:
|
||||
// otool -l ./path/to/bin | grep -A4 LC_MAIN
|
||||
// otool -l ./path/to/bin | grep -A4 LC_UNIXTHREAD
|
||||
for _, l := range f.Loads {
|
||||
data := l.Raw()
|
||||
cmd := f.ByteOrder.Uint32(data)
|
||||
|
||||
if macho.LoadCmd(cmd) == macho.LoadCmdUnixThread || macho.LoadCmd(cmd) == lcMain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func machoHasExports(f *macho.File) bool {
|
||||
for _, sym := range f.Symtab.Syms {
|
||||
// look for symbols that are:
|
||||
// - not private and are external
|
||||
// - do not have an N_TYPE value of N_UNDF (undefined symbol)
|
||||
//
|
||||
// here's the bit layout for the n_type field:
|
||||
// 0000 0000
|
||||
// ─┬─│ ─┬─│
|
||||
// │ │ │ └─ N_EXT (external symbol)
|
||||
// │ │ └─ N_TYPE (N_UNDF, N_ABS, N_SECT, N_PBUD, N_INDR)
|
||||
// │ └─ N_PEXT (private external symbol)
|
||||
// └─ N_STAB (debugging symbol)
|
||||
//
|
||||
isExternal := sym.Type&machoNExt == machoNExt
|
||||
isPrivate := sym.Type&machoNPExt == machoNPExt
|
||||
nTypeIsUndefined := sym.Type&0x0e == 0
|
||||
|
||||
if isExternal && !isPrivate {
|
||||
if sym.Name == "_main" || sym.Name == "__mh_execute_header" {
|
||||
// ...however there are some symbols that are not exported but are still important
|
||||
// for debugging or as an entrypoint, so we need to explicitly check for them
|
||||
continue
|
||||
}
|
||||
if nTypeIsUndefined {
|
||||
continue
|
||||
}
|
||||
// we have a symbol that is not private and is external
|
||||
// and is not undefined, so it is an export
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/macho"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func Test_machoHasEntrypoint(t *testing.T) {
|
||||
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/libhello.dylib",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello_mac",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := macho.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, machoHasEntrypoint(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_machoHasExports(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/libhello.dylib",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello_mac",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := macho.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, machoHasExports(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func findPEFeatures(data *file.Executable, reader unionreader.UnionReader) error {
|
||||
// TODO: support security features
|
||||
|
||||
f, err := pe.NewFile(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.ImportedLibraries = libs
|
||||
data.HasEntrypoint = peHasEntrypoint(f)
|
||||
data.HasExports = peHasExports(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
windowsExeEntrypoints = strset.New("main", "WinMain", "wWinMain")
|
||||
windowsDllEntrypoints = strset.New("DllMain", "_DllMainCRTStartup@12", "CRT_INIT")
|
||||
)
|
||||
|
||||
func peHasEntrypoint(f *pe.File) bool {
|
||||
// DLLs can have entrypoints, but they are not "executables" in the traditional sense,
|
||||
// but instead point to an initialization function (DLLMain).
|
||||
// The PE format does not require an entrypoint, so it is possible to not have one, however,
|
||||
// the microsoft C runtime does: https://learn.microsoft.com/en-US/troubleshoot/developer/visualstudio/cpp/libraries/use-c-run-time
|
||||
//
|
||||
// > When building a DLL which uses any of the C Run-time libraries, in order to ensure that the CRT is properly initialized, either
|
||||
// > 1. the initialization function must be named DllMain() and the entry point must be specified with the linker option -entry:_DllMainCRTStartup@12 - or -
|
||||
// > 2. the DLL's entry point must explicitly call CRT_INIT() on process attach and process detach
|
||||
//
|
||||
// This isn't really helpful from a user perspective when it comes to indicating if there is an entrypoint or not
|
||||
// since it will always effectively be true for DLLs! All DLLs and Executables (aka "modules") have a single
|
||||
// entrypoint, _GetPEImageBase, but we're more interested in the logical idea of an entrypoint.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/psapi/module-information for more details.
|
||||
|
||||
var hasLibEntrypoint, hasExeEntrypoint bool
|
||||
for _, s := range f.Symbols {
|
||||
if windowsExeEntrypoints.Has(s.Name) {
|
||||
hasExeEntrypoint = true
|
||||
}
|
||||
if windowsDllEntrypoints.Has(s.Name) {
|
||||
hasLibEntrypoint = true
|
||||
}
|
||||
}
|
||||
|
||||
switch v := f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint
|
||||
case *pe.OptionalHeader64:
|
||||
return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func peHasExports(f *pe.File) bool {
|
||||
if f.OptionalHeader == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch v := f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0
|
||||
case *pe.OptionalHeader64:
|
||||
return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func Test_peHasEntrypoint(t *testing.T) {
|
||||
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/hello.dll", // though this is a shared lib, it has an entrypoint (DLLMain)
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello.exe",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := pe.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, peHasEntrypoint(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_peHasExports(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "shared lib",
|
||||
fixture: "bin/hello.dll",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
fixture: "bin/hello.exe",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := pe.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, peHasExports(f))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bin
|
||||
actual_verify
|
||||
Dockerfile.sha256
|
||||
@@ -0,0 +1,3 @@
|
||||
../bin
|
||||
actual_verify
|
||||
Dockerfile.sha256
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM gcc:9.5.0
|
||||
|
||||
RUN apt update -y && apt install -y clang cmake git make m4 pkg-config zlib1g-dev
|
||||
|
||||
## from https://github.com/runsafesecurity/selfrando/blob/tb-v0.4.2/docs/linux-build-instructions.md
|
||||
#RUN git clone https://github.com/runsafesecurity/selfrando.git && \
|
||||
# export SR_ARCH=`uname -m | sed s/i686/x86/` && \
|
||||
# cd selfrando && \
|
||||
# cmake . -DSR_DEBUG_LEVEL=env -DCMAKE_BUILD_TYPE=Release -DSR_BUILD_LIBELF=1 \
|
||||
# -DSR_ARCH=$SR_ARCH -DSR_LOG=console \
|
||||
# -DSR_FORCE_INPLACE=1 -G "Unix Makefiles" \
|
||||
# -DCMAKE_INSTALL_PREFIX:PATH=$PWD/out/$SR_ARCH
|
||||
#RUN cd selfrando && make -j`nprocs --all`
|
||||
#RUN cd selfrando && make install
|
||||
|
||||
RUN curl -o /bin/checksec https://raw.githubusercontent.com/slimm609/checksec.sh/2.6.0/checksec && chmod +x /bin/checksec
|
||||
@@ -0,0 +1,34 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-bin-build-tools:latest
|
||||
VERIFY_FILE=actual_verify
|
||||
|
||||
all: build verify
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
# for selfrando...
|
||||
# docker buildx build --platform linux/amd64 -t $(TOOL_IMAGE) .
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
mkdir -p $(BIN)
|
||||
docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
verify: tools
|
||||
@rm -f $(VERIFY_FILE)
|
||||
docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make verify > $(VERIFY_FILE)
|
||||
@python ./differ expected_verify $(VERIFY_FILE)
|
||||
|
||||
debug:
|
||||
docker run -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
cache.fingerprint:
|
||||
@find project Dockerfile Makefile -type f -exec md5sum {} + | awk '{print $1}' | sort | tee cache.fingerprint
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/*
|
||||
|
||||
.PHONY: build verify debug build-image build-bins clean dockerfile-check cache.fingerprint
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r') as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
def diff_json(json1, json2):
|
||||
differences_found = False
|
||||
|
||||
for key in json1:
|
||||
if key not in json2:
|
||||
print(f'missing key "{key}"')
|
||||
continue
|
||||
|
||||
differences = []
|
||||
for subkey in json1[key]:
|
||||
if subkey not in json2[key]:
|
||||
differences.append(f' - "{subkey}": expected "{json1[key][subkey]}" but was MISSING')
|
||||
continue
|
||||
|
||||
if subkey in json2[key] and json1[key][subkey] != json2[key][subkey]:
|
||||
differences.append(f' - "{subkey}": expected "{json1[key][subkey]}" got "{json2[key][subkey]}"')
|
||||
|
||||
if differences:
|
||||
differences_found = True
|
||||
print(f'{key}')
|
||||
for diff in differences:
|
||||
print(diff)
|
||||
print()
|
||||
|
||||
return differences_found
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python ./differ file1.json file2.json")
|
||||
sys.exit(1)
|
||||
|
||||
json1 = load_json(sys.argv[1])
|
||||
json2 = load_json(sys.argv[2])
|
||||
|
||||
if diff_json(json1, json2):
|
||||
print("FAIL: unexpected security feature differences")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("PASS: all security features accounted for")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
{ "dir": { "name":"../bin" }, "../bin/no_protection": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"yes","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/no_protection"}, "../bin/with_nx": { "relro":"no","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_nx"}, "../bin/with_rpath": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"yes","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_rpath"}, "../bin/with_canary": { "relro":"no","canary":"yes","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_canary"}, "../bin/with_relro": { "relro":"full","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_relro"}, "../bin/with_pie": { "relro":"no","canary":"no","nx":"no","pie":"yes","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_pie"}, "../bin/with_runpath": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"yes","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_runpath"}, "../bin/with_safestack": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"yes","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"3", "filename":"../bin/with_safestack"}, "../bin/pie_false_positive.so": { "relro":"partial","canary":"no","nx":"yes","pie":"dso","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"0", "filename":"../bin/pie_false_positive.so"}, "../bin/with_cfi": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"yes","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_cfi"}, "../bin/with_partial_relro": { "relro":"partial","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_partial_relro"}, "../bin/protected": { "relro":"full","canary":"yes","nx":"yes","pie":"yes","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/protected"}, "../bin/with_fortify": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"yes","fortified":"1","fortify-able":"2", "filename":"../bin/with_fortify"}}
|
||||
@@ -0,0 +1,93 @@
|
||||
### GCC Options ############################################
|
||||
CANARY := -fstack-protector
|
||||
NO_CANARY := -fno-stack-protector
|
||||
|
||||
SHARED_OBJ := -shared
|
||||
|
||||
RELRO := -z relro -z now
|
||||
PARTIAL_RELRO := -z relro
|
||||
NO_RELRO := -z norelro
|
||||
|
||||
NX := -z noexecstack
|
||||
NO_NX := -z execstack
|
||||
|
||||
PIE := -fpic -pie
|
||||
NO_PIE := -no-pie
|
||||
|
||||
# deprecated
|
||||
RPATH := -Wl,--disable-new-dtags,-rpath,./libs
|
||||
|
||||
# replaces RPATH (thus us mutually exclusive with it)
|
||||
RUNPATH := -Wl,-rpath,./libs
|
||||
|
||||
GCCFLAGS := -g
|
||||
|
||||
### Clang Options ############################################
|
||||
|
||||
SAFE_STACK := -fsanitize=safe-stack
|
||||
|
||||
CFI := -flto -fvisibility=hidden -fsanitize=cfi
|
||||
|
||||
FORTIFY := -O2 -D_FORTIFY_SOURCE=2
|
||||
|
||||
### Common Options ############################################
|
||||
|
||||
SRC := main.c
|
||||
LIB_SRC := lib.c
|
||||
BIN := ../bin
|
||||
|
||||
BINS := $(BIN)/no_protection $(BIN)/with_nx $(BIN)/pie_false_positive.so $(BIN)/with_pie $(BIN)/with_canary $(BIN)/with_relro $(BIN)/with_partial_relro $(BIN)/with_rpath $(BIN)/with_runpath $(BIN)/with_safestack $(BIN)/with_cfi $(BIN)/with_fortify $(BIN)/protected
|
||||
#.PHONY: verify $(BIN)/no_protection $(BIN)/with_nx $(BIN)/pie_false_positive.so $(BIN)/with_pie $(BIN)/with_canary $(BIN)/with_relro $(BIN)/with_partial_relro $(BIN)/with_rpath $(BIN)/with_runpath $(BIN)/with_safestack $(BIN)/with_cfi $(BIN)/with_fortify $(BIN)/protected
|
||||
.PHONY: verify clean all
|
||||
|
||||
all: $(BINS)
|
||||
|
||||
|
||||
$(BIN)/no_protection : $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RUNPATH)
|
||||
|
||||
$(BIN)/with_nx : $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/pie_false_positive.so: $(LIB_SRC)
|
||||
gcc $< -c -Wall -Werror -fpic $(LIB_SRC)
|
||||
gcc -shared -o $@ lib.o ; rm lib.o
|
||||
|
||||
$(BIN)/with_pie: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(PIE)
|
||||
|
||||
$(BIN)/with_canary: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_relro: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_partial_relro: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(PARTIAL_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_rpath: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RPATH)
|
||||
|
||||
$(BIN)/with_runpath: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RUNPATH)
|
||||
|
||||
$(BIN)/with_safestack: $(SRC)
|
||||
clang $< -o $@ $(SAFE_STACK)
|
||||
|
||||
$(BIN)/with_cfi: $(SRC)
|
||||
clang $< -o $@ $(CFI)
|
||||
|
||||
$(BIN)/with_fortify: $(SRC)
|
||||
clang $< -o $@ $(FORTIFY)
|
||||
|
||||
#$(BIN)/with_selfrando: $(SRC)
|
||||
# srenv gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/protected: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(CANARY) $(NX) $(RELRO) $(PIE)
|
||||
|
||||
verify:
|
||||
@/bin/checksec --dir=$(BIN) --extended --output=json
|
||||
|
||||
clean:
|
||||
rm -rf $(BINS)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
void foo(void)
|
||||
{
|
||||
puts("Share me!");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef foo_h__
|
||||
#define foo_h__
|
||||
|
||||
extern void foo(void);
|
||||
|
||||
#endif // foo_h__
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user