Compare commits

..
Author SHA1 Message Date
Alex Goodman e841b03219 [wip] remove sqlite import
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-19 11:50:45 -04:00
Christopher Angelo Phillips b4ca04001c chore: update dockerfile base images to latest rolling tags (#3915)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-19 13:43:14 +00:00
anchore-actions-token-generator[bot]andwagoodman 828645ec27 chore(deps): update CPE dictionary index (#3913)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-05-19 09:16:25 -04:00
Alex Goodman db77b54c01 finalize go mod ref (#3908)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 17:36:26 +00:00
Alex Goodman 2d4fe513ec remove benchmark workflow (#3906)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 11:08:43 -04:00
Christopher Angelo Phillips e1374f758e fix: update license content filtering default case to be 'none' for no content returned
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-16 14:25:15 +00:00
dependabot[bot] 945893847f chore(deps): bump github/codeql-action from 3.28.17 to 3.28.18 (#3905)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.17 to 3.28.18.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/60168efe1c415ce0f5521ea06d5c2062adbeed1b...ff0a06e83cb2de871e5a09832bc6a81e7276941f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 14:16:11 +00:00
sathiya06andAlex Goodman 8cbdd38a63 fix: Make Native Image contains no embedded SBOM Error Discoverable (#3805)
* fix: Make Native Image contains no embedded SBOM Error Discoverable

Signed-off-by: Sathiya Narayanan Venkatesan <sathiyavenkat06@gmail.com>

* adjust error phrasing + wrap error

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Sathiya Narayanan Venkatesan <sathiyavenkat06@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 13:54:40 +00:00
Adam McClenaghanandAlex Goodman 8f02bd85f6 fix: Distinguish openjdk vs jdk when using file source (#3895)
* fix: Distinguish openjdk vs jdk when using file source

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>

* fix: Fix goimport order

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>

* add comment

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: adammcclenaghan <adam@mcclenaghan.co.uk>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-16 13:29:53 +00:00
Alex Goodman 0480b516f6 chore: fix publishing test fixture images (#3896)
* update pkg counts

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump max cache size

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 14:35:11 -04:00
Christopher Angelo Phillips 2a055690e6 chore: delete unused fixture (#3901)
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-15 17:30:36 +00:00
Christopher Angelo Phillips 4f73d35051 Include default config licenses (#3900)
* fix: the licenses config was not being carried through causing content to show by default
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-05-15 16:48:18 +00:00
Ralph BeanandAlex Goodman b369b02f4f Expose RPM signature information (for RPM DB and RPM archives) (#3179)
* feat: expose rpm signature information

This helps with more confident identification of an rpm.

In theory, two rpms can be built that have the same purl string, and
otherwise look identical in syft's output, but the PGP information
would distinguish them as signed either by different keys, or signed at
different times.

In practice, this usually makes no difference since rpms tend to have
unique name/version/release strings. This just gives increased
confidence about the identity of the rpm found in the db.

Signed-off-by: Ralph Bean <rbean@redhat.com>

* chore: generate json schema

Signed-off-by: Ralph Bean <rbean@redhat.com>

* re-generate json schema

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename to a more generic signature field

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename rpm.pgp to rpm.signatures

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* split out signature fields

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump json schema

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* include RPM archives

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update json schema

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* dont fail on unknown signature type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Ralph Bean <rbean@redhat.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 16:01:00 +00:00
dependabot[bot] 5effed06a8 chore(deps): bump github.com/mholt/archives from 0.1.1 to 0.1.2 (#3898)
Bumps [github.com/mholt/archives](https://github.com/mholt/archives) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/mholt/archives/releases)
- [Commits](https://github.com/mholt/archives/compare/v0.1.1...v0.1.2)

---
updated-dependencies:
- dependency-name: github.com/mholt/archives
  dependency-version: 0.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 10:23:30 -04:00
dependabot[bot] 5e25d52845 chore(deps): bump anchore/sbom-action from 0.19.0 to 0.20.0 (#3899)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/9f7302141466aa6482940f15371237e9d9f4c34a...e11c554f704a0b820cbf8c51673f6945e0731532)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 10:23:20 -04:00
Laurent GoderreandAlex Goodman a8e5b25632 Add PHP interpreter + extensions cataloger (#2585)
* Add PHP extensions binary classifiers

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

* [wip] add php extensions cataloger

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* find interpreters + extension

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* internalize binary cataloger utilities

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* default to linux/amd64 for test fixtures

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-15 08:22:50 -04:00
Alex Goodman 0521ccaf5e chore: update fixtures based on CI builds (#3894)
* update fixtures based on ci builds

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix typo

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-05-14 21:30:20 +00:00
62 changed files with 5079 additions and 761 deletions
-62
View File
@@ -1,62 +0,0 @@
name: "Benchmark testing"
on:
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
Benchmark-Test:
name: "Benchmark tests"
runs-on: ubuntu-24.04
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Run benchmark tests
id: benchmark
run: |
REF_NAME=${GITHUB_REF##*/} make benchmark
OUTPUT=$(make show-benchstat)
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db #v2.9.2
continue-on-error: true
with:
header: benchmark
message: |
### Benchmark Test Results
<details>
<summary>Benchmark results from the latest changes vs base branch</summary>
```
${{ steps.benchmark.outputs.result }}
```
</details>
+3 -3
View File
@@ -45,7 +45,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18
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@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18
# ℹ️ 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@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18
+1 -1
View File
@@ -148,7 +148,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@9f7302141466aa6482940f15371237e9d9f4c34a #v0.19.0
- uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 #v0.20.0
continue-on-error: true
with:
file: go.mod
+243 -246
View File
@@ -14,12 +14,9 @@ builds:
dir: ./cmd/syft
binary: syft
goos:
- linux
- windows
goarch:
- amd64
- arm64
- ppc64le
- s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
ldflags: &build-ldflags |
@@ -31,246 +28,246 @@ builds:
-X main.buildDate={{.Date}}
-X main.gitDescription={{.Summary}}
- id: darwin-build
dir: ./cmd/syft
binary: syft
goos:
- darwin
goarch:
- amd64
- arm64
mod_timestamp: *build-timestamp
ldflags: *build-ldflags
hooks:
post:
- cmd: .tool/quill sign-and-notarize "{{ .Path }}" --dry-run={{ .IsSnapshot }} --ad-hoc={{ .IsSnapshot }} -vv
env:
- QUILL_LOG_FILE=/tmp/quill-{{ .Target }}.log
# - id: darwin-build
# dir: ./cmd/syft
# binary: syft
# goos:
# - darwin
# goarch:
# - amd64
# - arm64
# mod_timestamp: *build-timestamp
# ldflags: *build-ldflags
# hooks:
# post:
# - cmd: .tool/quill sign-and-notarize "{{ .Path }}" --dry-run={{ .IsSnapshot }} --ad-hoc={{ .IsSnapshot }} -vv
# env:
# - QUILL_LOG_FILE=/tmp/quill-{{ .Target }}.log
#
# - id: windows-build
# dir: ./cmd/syft
# binary: syft
# goos:
# - windows
# goarch:
# - amd64
# mod_timestamp: *build-timestamp
# ldflags: *build-ldflags
- id: windows-build
dir: ./cmd/syft
binary: syft
goos:
- windows
goarch:
- amd64
mod_timestamp: *build-timestamp
ldflags: *build-ldflags
archives:
- id: linux-archives
builds:
- linux-build
# note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
- id: darwin-archives
builds:
- darwin-build
- id: windows-archives
format: zip
builds:
- windows-build
nfpms:
- license: "Apache 2.0"
maintainer: "Anchore, Inc"
homepage: &website "https://github.com/anchore/syft"
description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
formats:
- rpm
- deb
brews:
- repository:
owner: anchore
name: homebrew-syft
token: "{{.Env.GITHUB_BREW_TOKEN}}"
ids:
- darwin-archives
- linux-archives
homepage: *website
description: *description
license: "Apache License 2.0"
dockers:
- image_templates:
- anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:debug
- ghcr.io/anchore/syft:{{.Tag}}-debug
goarch: amd64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-arm64v8
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
goarch: arm64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-ppc64le
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-s390x
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
goarch: s390x
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:latest
- anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:latest
- ghcr.io/anchore/syft:{{.Tag}}
goarch: amd64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
goarch: ppc64le
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-s390x
- ghcr.io/anchore/syft:{{.Tag}}-s390x
goarch: s390x
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
sboms:
- artifacts: archive
# this is relative to the snapshot/dist directory, not the root of the repo
cmd: ../.tool/syft
documents:
- "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
args:
- "scan"
- "$artifact"
- "--output"
- "json=$document"
signs:
- cmd: .tool/cosign
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args:
- "sign-blob"
- "--oidc-issuer=https://token.actions.githubusercontent.com"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
#archives:
# - id: linux-archives
# builds:
# - linux-build
#
# # note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
# - id: darwin-archives
# builds:
# - darwin-build
#
# - id: windows-archives
# format: zip
# builds:
# - windows-build
#
#nfpms:
# - license: "Apache 2.0"
# maintainer: "Anchore, Inc"
# homepage: &website "https://github.com/anchore/syft"
# description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
# formats:
# - rpm
# - deb
#
#brews:
# - repository:
# owner: anchore
# name: homebrew-syft
# token: "{{.Env.GITHUB_BREW_TOKEN}}"
# ids:
# - darwin-archives
# - linux-archives
# homepage: *website
# description: *description
# license: "Apache License 2.0"
#
#dockers:
# - image_templates:
# - anchore/syft:debug
# - anchore/syft:{{.Tag}}-debug
# - ghcr.io/anchore/syft:debug
# - ghcr.io/anchore/syft:{{.Tag}}-debug
# goarch: amd64
# dockerfile: Dockerfile.debug
# use: buildx
# build_flag_templates:
# - "--platform=linux/amd64"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:debug-arm64v8
# - anchore/syft:{{.Tag}}-debug-arm64v8
# - ghcr.io/anchore/syft:debug-arm64v8
# - ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
# goarch: arm64
# dockerfile: Dockerfile.debug
# use: buildx
# build_flag_templates:
# - "--platform=linux/arm64/v8"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:debug-ppc64le
# - anchore/syft:{{.Tag}}-debug-ppc64le
# - ghcr.io/anchore/syft:debug-ppc64le
# - ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
# goarch: ppc64le
# dockerfile: Dockerfile.debug
# use: buildx
# build_flag_templates:
# - "--platform=linux/ppc64le"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:debug-s390x
# - anchore/syft:{{.Tag}}-debug-s390x
# - ghcr.io/anchore/syft:debug-s390x
# - ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
# goarch: s390x
# dockerfile: Dockerfile.debug
# use: buildx
# build_flag_templates:
# - "--platform=linux/s390x"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:latest
# - anchore/syft:{{.Tag}}
# - ghcr.io/anchore/syft:latest
# - ghcr.io/anchore/syft:{{.Tag}}
# goarch: amd64
# dockerfile: Dockerfile
# use: buildx
# build_flag_templates:
# - "--platform=linux/amd64"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:{{.Tag}}-arm64v8
# - ghcr.io/anchore/syft:{{.Tag}}-arm64v8
# goarch: arm64
# dockerfile: Dockerfile
# use: buildx
# build_flag_templates:
# - "--platform=linux/arm64/v8"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:{{.Tag}}-ppc64le
# - ghcr.io/anchore/syft:{{.Tag}}-ppc64le
# goarch: ppc64le
# dockerfile: Dockerfile
# use: buildx
# build_flag_templates:
# - "--platform=linux/ppc64le"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
# - image_templates:
# - anchore/syft:{{.Tag}}-s390x
# - ghcr.io/anchore/syft:{{.Tag}}-s390x
# goarch: s390x
# dockerfile: Dockerfile
# use: buildx
# build_flag_templates:
# - "--platform=linux/s390x"
# - "--build-arg=BUILD_DATE={{.Date}}"
# - "--build-arg=BUILD_VERSION={{.Version}}"
# - "--build-arg=VCS_REF={{.FullCommit}}"
# - "--build-arg=VCS_URL={{.GitURL}}"
#
#docker_manifests:
# - name_template: anchore/syft:latest
# image_templates:
# - anchore/syft:{{.Tag}}
# - anchore/syft:{{.Tag}}-arm64v8
# - anchore/syft:{{.Tag}}-ppc64le
# - anchore/syft:{{.Tag}}-s390x
#
# - name_template: anchore/syft:debug
# - anchore/syft:{{.Tag}}-debug
# - anchore/syft:{{.Tag}}-debug-arm64v8
# - anchore/syft:{{.Tag}}-debug-ppc64le
# - anchore/syft:{{.Tag}}-debug-s390x
#
# - name_template: anchore/syft:{{.Tag}}
# image_templates:
# - anchore/syft:{{.Tag}}
# - anchore/syft:{{.Tag}}-arm64v8
# - anchore/syft:{{.Tag}}-ppc64le
# - anchore/syft:{{.Tag}}-s390x
#
# - name_template: ghcr.io/anchore/syft:latest
# image_templates:
# - ghcr.io/anchore/syft:{{.Tag}}
# - ghcr.io/anchore/syft:{{.Tag}}-arm64v8
# - ghcr.io/anchore/syft:{{.Tag}}-ppc64le
# - ghcr.io/anchore/syft:{{.Tag}}-s390x
#
# - name_template: ghcr.io/anchore/syft:debug
# image_templates:
# - ghcr.io/anchore/syft:{{.Tag}}-debug
# - ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
# - ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
# - ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
#
# - name_template: ghcr.io/anchore/syft:{{.Tag}}
# image_templates:
# - ghcr.io/anchore/syft:{{.Tag}}
# - ghcr.io/anchore/syft:{{.Tag}}-arm64v8
# - ghcr.io/anchore/syft:{{.Tag}}-ppc64le
# - ghcr.io/anchore/syft:{{.Tag}}-s390x
#
#sboms:
# - artifacts: archive
# # this is relative to the snapshot/dist directory, not the root of the repo
# cmd: ../.tool/syft
# documents:
# - "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
# args:
# - "scan"
# - "$artifact"
# - "--output"
# - "json=$document"
#
#signs:
# - cmd: .tool/cosign
# signature: "${artifact}.sig"
# certificate: "${artifact}.pem"
# args:
# - "sign-blob"
# - "--oidc-issuer=https://token.actions.githubusercontent.com"
# - "--output-certificate=${certificate}"
# - "--output-signature=${signature}"
# - "${artifact}"
# - "--yes"
# artifacts: checksum
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2 AS build
FROM gcr.io/distroless/static-debian12:latest AS build
FROM scratch
# needed for version check HTTPS request
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2
FROM gcr.io/distroless/static-debian12:debug
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
+2 -3
View File
@@ -75,7 +75,6 @@ tasks:
- task: unit
- task: integration
- task: validate-cyclonedx-schema
- task: benchmark
- task: test-utils
- task: cli
- task: check-docker-cache
@@ -453,8 +452,8 @@ tasks:
find . | grep cache | grep tar | xargs du
echo "total $total_size KB"
if [ "$total_size" -gt 1048576 ]; then
echo 'docker cache is larger than 1GB'
if [ "$total_size" -gt 5242880 ]; then
echo 'docker cache is larger than 5GB'
exit 1
fi
+6 -38
View File
@@ -4,18 +4,13 @@ import (
"fmt"
"github.com/anchore/clio"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/cataloging"
)
type licenseConfig struct {
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
// Deprecated: please use include-license-content instead
IncludeUnknownLicenseContent *bool `yaml:"-" json:"-" mapstructure:"include-unknown-license-content"`
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
// Deprecated: please use coverage instead
LicenseCoverage *float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
AvailableLicenseContent []cataloging.LicenseContent `yaml:"-" json:"-" mapstructure:"-"`
}
@@ -25,43 +20,16 @@ var _ interface {
func (o *licenseConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Content, fmt.Sprintf("include the content of licenses in the SBOM for a given syft scan; valid values are: %s", o.AvailableLicenseContent))
descriptions.Add(&o.IncludeUnknownLicenseContent, `deprecated: please use 'license-content' instead`)
descriptions.Add(&o.Coverage, `adjust the percent as a fraction of the total text, in normalized words, that
matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched.`)
descriptions.Add(&o.LicenseCoverage, `deprecated: please use 'coverage' instead`)
}
func (o *licenseConfig) PostLoad() error {
cfg := cataloging.DefaultLicenseConfig()
defaultContent := cfg.IncludeContent
defaultCoverage := cfg.Coverage
// if both legacy and new fields are specified, error out
if o.IncludeUnknownLicenseContent != nil && o.Content != defaultContent {
return fmt.Errorf("both 'include-unknown-license-content' and 'content' are set, please use only 'content'")
validContent := internal.NewSet(o.AvailableLicenseContent...)
if !validContent.Contains(o.Content) {
return fmt.Errorf("could not use %q as license content option; valid values are: %v", o.Content, validContent.ToSlice())
}
if o.LicenseCoverage != nil && o.Coverage != defaultCoverage {
return fmt.Errorf("both 'license-coverage' and 'coverage' are set, please use only 'coverage'")
}
// finalize the license content value
if o.IncludeUnknownLicenseContent != nil {
// convert 'include-unknown-license-content' -> 'license-content'
v := cataloging.LicenseContentExcludeAll
if *o.IncludeUnknownLicenseContent {
v = cataloging.LicenseContentIncludeUnknown
}
o.Content = v
}
// finalize the coverage value
if o.LicenseCoverage != nil {
// convert 'license-coverage' -> 'coverage'
o.Coverage = *o.LicenseCoverage
}
return nil
}
@@ -19,7 +19,7 @@ func TestPackageDeduplication(t *testing.T) {
}{
{
scope: source.AllLayersScope,
packageCount: 178, // without deduplication this would be ~600
packageCount: 175, // without deduplication this would be ~600
instanceCount: map[string]int{
"basesystem": 1,
"wget": 1,
@@ -30,15 +30,15 @@ func TestPackageDeduplication(t *testing.T) {
locationCount: map[string]int{
"basesystem-11-13.el9": 5, // in all layers
"curl-minimal-7.76.1-26.el9_3.2.0.1": 2, // base + wget layer
"curl-minimal-7.76.1-29.el9_4.1": 3, // curl upgrade layer + all above layers
"curl-minimal-7.76.1-31.el9": 3, // curl upgrade layer + all above layers
"wget-1.21.1-8.el9_4": 4, // wget + all above layers
"vsftpd-3.0.5-5.el9": 2, // vsftpd + all above layers
"httpd-2.4.57-11.el9_4.1": 1, // last layer
"vsftpd-3.0.5-6.el9": 2, // vsftpd + all above layers
"httpd-2.4.62-1.el9_5.2": 1, // last layer
},
},
{
scope: source.SquashedScope,
packageCount: 172,
packageCount: 169,
instanceCount: map[string]int{
"basesystem": 1,
"wget": 1,
@@ -47,11 +47,11 @@ func TestPackageDeduplication(t *testing.T) {
"httpd": 1, // rpm, binary is now excluded by overlap
},
locationCount: map[string]int{
"basesystem-11-13.el9": 1,
"curl-minimal-7.76.1-29.el9_4.1": 1, // upgrade
"wget-1.21.1-8.el9_4": 1,
"vsftpd-3.0.5-5.el9": 1,
"httpd-2.4.57-11.el9_4.1": 1,
"basesystem-11-13.el9": 1,
"curl-minimal-7.76.1-31.el9": 1, // upgrade
"wget-1.21.1-8.el9_4": 1,
"vsftpd-3.0.5-6.el9": 1,
"httpd-2.4.62-1.el9_5.2": 1,
},
},
}
@@ -3,18 +3,18 @@ package integration
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope)
var packages []pkg.Package
for p := range sbom.Artifacts.Packages.Enumerate() {
packages = append(packages, p)
var count int
for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) {
count++
}
if len(packages) < 1 {
t.Errorf("failed to find packages for photon distro; wanted > 0 got 0")
}
assert.Greater(t, count, 0, "expected to find RPM packages in the SBOM (but did not)")
}
@@ -3,8 +3,6 @@ package integration
import (
"testing"
_ "modernc.org/sqlite"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
-2
View File
@@ -1,8 +1,6 @@
package main
import (
_ "modernc.org/sqlite"
"github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/cli"
"github.com/anchore/syft/cmd/syft/internal"
+7 -10
View File
@@ -19,6 +19,7 @@ require (
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
@@ -57,9 +58,8 @@ require (
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/knqyf263/go-rpmdb v0.1.1
github.com/magiconair/properties v1.8.10
github.com/mholt/archives v0.1.1
github.com/mholt/archives v0.1.2
github.com/moby/sys/mountinfo v0.7.2
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
github.com/olekukonko/tablewriter v0.0.5
@@ -89,7 +89,7 @@ require (
golang.org/x/mod v0.24.0
golang.org/x/net v0.40.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.37.0
modernc.org/sqlite v1.37.0 // indirect
)
require (
@@ -100,11 +100,11 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
@@ -191,7 +191,6 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
@@ -204,7 +203,6 @@ require (
github.com/pkg/errors v0.9.1 // indirect
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.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
@@ -255,11 +253,10 @@ require (
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.62.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect
)
retract (
v1.25.0 // published with a replace directive (confusing for API users)
v0.53.2
v0.53.1 // Published accidentally with incorrect license in depdencies
v0.53.1 // published accidentally with incorrect license in depdencies
)
+8 -24
View File
@@ -78,8 +78,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
@@ -110,6 +110,8 @@ github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io=
@@ -123,8 +125,8 @@ github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:
github.com/anchore/stereoscope v0.1.4 h1:e+iT9UdUzLBabWGe84hn5sTHDRioY+4IHsVzJXuJlek=
github.com/anchore/stereoscope v0.1.4/go.mod h1:omWgXDEp/XfqCJlZXIByEo1c3ArZg/qTJ5LBKVLAIdw=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -548,8 +550,6 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584=
github.com/knqyf263/go-rpmdb v0.1.1/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=
@@ -600,8 +600,8 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
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=
github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY=
github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I=
github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A=
github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -1390,30 +1390,14 @@ 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=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
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=
+1 -1
View File
@@ -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 = "16.0.32"
JSONSchemaVersion = "16.0.33"
)
+5 -5
View File
@@ -278,16 +278,16 @@ func applyLicenseContentRules(p *pkg.Package, cfg cataloging.LicenseConfig) {
l := &licenses[i]
switch cfg.IncludeContent {
case cataloging.LicenseContentIncludeUnknown:
// we don't have an SPDX expression, which means we didn't find an SPDX license
// include the unknown licenses content in the final SBOM
// we have an SPDX expression, which means this is NOT an unknown license
// remove the content, we are only including content for unknown licenses by default
if l.SPDXExpression != "" {
licenses[i].Contents = ""
}
case cataloging.LicenseContentExcludeAll:
// clear it all out
licenses[i].Contents = ""
case cataloging.LicenseContentIncludeAll:
// always include the content
default:
// clear it all out
licenses[i].Contents = ""
}
}
+36 -1
View File
@@ -174,7 +174,7 @@ func TestApplyLicenseContentRules(t *testing.T) {
},
},
{
name: "IncludeLicenseContentDefault",
name: "LicenseContentIncludeAll",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
@@ -193,6 +193,41 @@ func TestApplyLicenseContentRules(t *testing.T) {
},
},
},
{
name: "default license config should be LicenseContentExcludeAll",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.DefaultLicenseConfig(),
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
},
{
Value: "License-Not-A-SPDX-Expression",
},
},
},
{
name: "invalid license content cataloging config results in the default case",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContent("invalid"),
},
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
},
{
Value: "License-Not-A-SPDX-Expression",
Contents: "", // content all removed
},
},
},
{
name: "Empty licenses",
inputLicenses: []pkg.License{},
+1
View File
@@ -164,6 +164,7 @@ func DefaultPackageTaskFactories() Factories {
},
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
),
newSimplePackageTaskFactory(php.NewInterpreterCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "php"),
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
File diff suppressed because it is too large Load Diff
+36 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.32/document",
"$id": "anchore.io/schema/syft/json/16.0.33/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -2677,6 +2677,12 @@
"sourceRpm": {
"type": "string"
},
"signatures": {
"items": {
"$ref": "#/$defs/RpmSignature"
},
"type": "array"
},
"size": {
"type": "integer"
},
@@ -2745,6 +2751,12 @@
"sourceRpm": {
"type": "string"
},
"signatures": {
"items": {
"$ref": "#/$defs/RpmSignature"
},
"type": "array"
},
"size": {
"type": "integer"
},
@@ -2821,6 +2833,29 @@
"flags"
]
},
"RpmSignature": {
"properties": {
"algo": {
"type": "string"
},
"hash": {
"type": "string"
},
"created": {
"type": "string"
},
"issuer": {
"type": "string"
}
},
"type": "object",
"required": [
"algo",
"hash",
"created",
"issuer"
]
},
"RubyGemspec": {
"properties": {
"name": {
-4
View File
@@ -14,10 +14,6 @@ const (
)
type LicenseConfig struct {
// IncludeUnknownLicenseContent controls whether the content of a license should be included in the SBOM when the license ID cannot be determined.
// Deprecated: use IncludeContent instead
IncludeUnknownLicenseContent bool `json:"-" yaml:"-" mapstructure:"-"`
// IncludeContent controls whether license copy discovered should be included in the SBOM.
IncludeContent LicenseContent `json:"include-content" yaml:"include-content" mapstructure:"include-content"`
+1
View File
@@ -255,6 +255,7 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t
RelationshipsConfig: c.Relationships,
DataGenerationConfig: c.DataGeneration,
PackagesConfig: c.Packages,
LicenseConfig: c.Licenses,
ComplianceConfig: c.Compliance,
FilesConfig: c.Files,
}
@@ -958,7 +958,7 @@ func Test_otherLicenses(t *testing.T) {
{
LicenseIdentifier: "LicenseRef-3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
// Carries through the syft-json license value when we shasum large texts
LicenseName: "LicenseRef-sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
LicenseName: "sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
ExtractedText: strings.TrimSpace(bigText),
},
},
+9 -1
View File
@@ -94,7 +94,15 @@ func redactor(values ...string) testutil.Redactor {
testutil.PatternReplacement{
// only the source component bom-ref (not package or other component bom-refs)
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="file">`),
Groups: []string{"redact"}, // use the regex to anchore the search, but only replace bytes within the capture group
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
Replace: "redacted",
},
).
WithPatternRedactorSpec(
testutil.PatternReplacement{
// only the source component bom-ref (not package or other component bom-refs)
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="container">`),
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
Replace: "redacted",
},
).
@@ -73,7 +73,7 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense, otherL
for _, l := range raw {
candidate := createSPDXLicense(l)
// isCustomLicense determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
// this determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
// of the SPDX spec, where:
// - we should not have a complex SPDX expression
// - if a single license, it should not be a known license (on the SPDX license list)
@@ -13,12 +13,13 @@ import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
const catalogerName = "binary-classifier-cataloger"
type ClassifierCatalogerConfig struct {
Classifiers []Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
Classifiers []binutils.Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
}
func DefaultClassifierCatalogerConfig() ClassifierCatalogerConfig {
@@ -48,7 +49,7 @@ func (cfg ClassifierCatalogerConfig) MarshalJSON() ([]byte, error) {
// related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such
// as busybox.
type cataloger struct {
classifiers []Classifier
classifiers []binutils.Classifier
}
// Name returns a string that uniquely describes the cataloger
@@ -101,7 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
target.Metadata = meta
}
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
func catalog(resolver file.Resolver, cls binutils.Classifier) (packages []pkg.Package, err error) {
var errs error
locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil {
@@ -109,7 +110,7 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
return nil, err
}
for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
errs = unknown.Append(errs, location, err)
continue
@@ -20,6 +20,7 @@ import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/directorysource"
"github.com/anchore/syft/syft/source/stereoscopesource"
@@ -248,45 +249,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("httpd-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-cli/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "php-cli",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php-cli@8.2.1",
Locations: locations("php"),
Metadata: metadata("php-cli-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-fpm/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "php-fpm",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php-fpm@8.2.1",
Locations: locations("php-fpm"),
Metadata: metadata("php-fpm-binary"),
},
},
{
// TODO: find original binary...
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
logicalFixture: "php-apache/8.2.1/linux-amd64",
expected: pkg.Package{
Name: "libphp",
Version: "8.2.1",
Type: "binary",
PURL: "pkg:generic/php@8.2.1",
Locations: locations("libphp.so"),
Metadata: metadata("php-apache-binary"),
},
},
{
// TODO: original binary is different than whats in config.yaml
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
@@ -1497,11 +1459,13 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
Locations: locations("foo"),
Metadata: metadata("foo-binary"),
}
fooClassifier := Classifier{
fooClassifier := binutils.Classifier{
Class: "foo-binary",
FileGlob: "**/foo",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
EvidenceMatcher: binutils.FileContentsVersionMatcher(
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
catalogerName,
),
Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"),
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
@@ -1516,7 +1480,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
{
name: "empty-negative",
config: ClassifierCatalogerConfig{
Classifiers: []Classifier{},
Classifiers: []binutils.Classifier{},
},
fixtureDir: "test-fixtures/custom/go-1.14",
expected: nil,
@@ -1532,7 +1496,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
{
name: "nodefault-negative",
config: ClassifierCatalogerConfig{
Classifiers: []Classifier{fooClassifier},
Classifiers: []binutils.Classifier{fooClassifier},
},
fixtureDir: "test-fixtures/custom/go-1.14",
expected: nil,
@@ -1541,7 +1505,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
name: "default-extended-positive",
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
append([]binutils.Classifier{}, defaultClassifers...),
fooClassifier,
),
},
@@ -1553,11 +1517,11 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
Classifier{
append([]binutils.Classifier{}, defaultClassifers...),
binutils.Classifier{
Class: "foo-binary",
FileGlob: "**/foo",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)not there`),
EvidenceMatcher: binutils.FileContentsVersionMatcher(`(?m)not there`, catalogerName),
Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"),
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
@@ -1571,7 +1535,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
name: "default-cutsom-positive",
config: ClassifierCatalogerConfig{
Classifiers: append(
append([]Classifier{}, defaultClassifers...),
append([]binutils.Classifier{}, defaultClassifers...),
fooClassifier,
),
},
@@ -1771,11 +1735,11 @@ func TestCatalogerConfig_MarshalJSON(t *testing.T) {
{
name: "only show names of classes",
cfg: ClassifierCatalogerConfig{
Classifiers: []Classifier{
Classifiers: []binutils.Classifier{
{
Class: "class",
FileGlob: "glob",
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
EvidenceMatcher: binutils.FileContentsVersionMatcher(".thing", catalogerName),
Package: "pkg",
PURL: packageurl.PackageURL{
Type: "type",
+121 -123
View File
@@ -1,22 +1,44 @@
package binary
import (
"fmt"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
//nolint:funlen
func DefaultClassifiers() []Classifier {
return []Classifier{
func DefaultClassifiers() []binutils.Classifier {
m := binutils.ContextualEvidenceMatchers{CatalogerName: catalogerName}
var libpythonMatcher = m.FileNameTemplateVersionMatcher(
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
pythonVersionTemplate,
)
var rubyMatcher = m.FileContentsVersionMatcher(
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
return []binutils.Classifier{
{
Class: "python-binary",
FileGlob: "**/python*",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// try to find version information from libpython shared libraries
sharedLibraryLookup(
binutils.SharedLibraryLookup(
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
libpythonMatcher),
// check for version information in the binary
fileNameTemplateVersionMatcher(
m.FileNameTemplateVersionMatcher(
`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
pythonVersionTemplate),
),
@@ -41,7 +63,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "pypy-binary-lib",
FileGlob: "**/libpypy*.so*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\[PyPy (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "pypy",
PURL: mustPURL("pkg:generic/pypy@version"),
@@ -49,7 +71,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "go-binary",
FileGlob: "**/go",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
@@ -58,7 +80,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "julia-binary",
FileGlob: "**/libjulia-internal.so",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)__init__\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00verify`),
Package: "julia",
PURL: mustPURL("pkg:generic/julia@version"),
@@ -67,7 +89,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "helm",
FileGlob: "**/helm",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "helm",
PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
@@ -76,13 +98,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "redis-binary",
FileGlob: "**/redis-server",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
),
Package: "redis",
PURL: mustPURL("pkg:generic/redis@version"),
@@ -94,13 +116,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-openjdk",
FileGlob: "**/java",
EvidenceMatcher: matchExcluding(
evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.MatchExcluding(
binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
@@ -115,7 +137,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-ibm",
FileGlob: "**/java",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL]
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
Package: "java/jre",
@@ -125,8 +147,8 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-oracle",
FileGlob: "**/java",
EvidenceMatcher: matchExcluding(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.MatchExcluding(
m.FileContentsVersionMatcher(
// [NUL]19.0.1+10-21[NUL]
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`),
// don't match openjdk
@@ -139,7 +161,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-graalvm",
FileGlob: "**/java",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
Package: "java/graalvm",
PURL: mustPURL("pkg:generic/java/graalvm@version"),
@@ -148,7 +170,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "java-binary-jdk",
FileGlob: "**/jdb",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
Package: "java/jdk",
PURL: mustPURL("pkg:generic/java/jdk@version"),
@@ -157,13 +179,13 @@ func DefaultClassifiers() []Classifier {
{
Class: "nodejs-binary",
FileGlob: "**/node",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// [NUL]node v0.10.48[NUL]
// [NUL]v0.12.18[NUL]
// [NUL]v4.9.1[NUL]
// node.js/v22.9.0
FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
m.FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
m.FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
),
Package: "node",
PURL: mustPURL("pkg:generic/node@version"),
@@ -172,7 +194,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "go-binary-hint",
FileGlob: "**/VERSION*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?(-[0-9a-f]{7})?)`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
@@ -181,7 +203,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "busybox-binary",
FileGlob: "**/busybox",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "busybox",
PURL: mustPURL("pkg:generic/busybox@version"),
@@ -190,7 +212,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "util-linux-binary",
FileGlob: "**/getopt",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00util-linux\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "util-linux",
PURL: mustPURL("pkg:generic/util-linux@version"),
@@ -199,10 +221,10 @@ func DefaultClassifiers() []Classifier {
{
Class: "haproxy-binary",
FileGlob: "**/haproxy",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
),
Package: "haproxy",
PURL: mustPURL("pkg:generic/haproxy@version"),
@@ -211,44 +233,16 @@ func DefaultClassifiers() []Classifier {
{
Class: "perl-binary",
FileGlob: "**/perl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\/usr\/local\/lib\/perl\d\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "perl",
PURL: mustPURL("pkg:generic/perl@version"),
CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-cli-binary",
FileGlob: "**/php*",
EvidenceMatcher: fileNameTemplateVersionMatcher(
`(.*/|^)php[0-9]*$`,
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-cli",
PURL: mustPURL("pkg:generic/php-cli@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-fpm-binary",
FileGlob: "**/php-fpm*",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-fpm",
PURL: mustPURL("pkg:generic/php-fpm@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-apache-binary",
FileGlob: "**/libphp*.so",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "libphp",
PURL: mustPURL("pkg:generic/php@version"),
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "php-composer-binary",
FileGlob: "**/composer*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)'pretty_version'\s*=>\s*'(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`),
Package: "composer",
PURL: mustPURL("pkg:generic/composer@version"),
@@ -257,7 +251,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "httpd-binary",
FileGlob: "**/httpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)Apache\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "httpd",
PURL: mustPURL("pkg:generic/httpd@version"),
@@ -266,7 +260,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "memcached-binary",
FileGlob: "**/memcached",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)memcached\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "memcached",
PURL: mustPURL("pkg:generic/memcached@version"),
@@ -275,7 +269,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "traefik-binary",
FileGlob: "**/traefik",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]v1.7.34[NUL]
// [NUL]2.9.6[NUL]
// 3.0.4[NUL]
@@ -287,7 +281,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "arangodb-binary",
FileGlob: "**/arangosh",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
Package: "arangodb",
PURL: mustPURL("pkg:generic/arangodb@version"),
@@ -296,7 +290,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "postgresql-binary",
FileGlob: "**/postgres",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]PostgreSQL 15beta4
// [NUL]PostgreSQL 15.1
// [NUL]PostgreSQL 9.6.24
@@ -309,11 +303,11 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
// shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc
FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
// /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc
FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
m.FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
),
Package: "mysql",
PURL: mustPURL("pkg:generic/mysql@version"),
@@ -322,7 +316,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/percona-server-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-server",
PURL: mustPURL("pkg:generic/percona-server@version"),
@@ -334,7 +328,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mysql-binary",
FileGlob: "**/mysql",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/Percona-XtraDB-Cluster-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-xtradb-cluster",
PURL: mustPURL("pkg:generic/percona-xtradb-cluster@version"),
@@ -347,7 +341,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "xtrabackup-binary",
FileGlob: "**/xtrabackup",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m).*/percona-xtrabackup-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
Package: "percona-xtrabackup",
PURL: mustPURL("pkg:generic/percona-xtrabackup@version"),
@@ -356,7 +350,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "mariadb-binary",
FileGlob: "**/{mariadb,mysql}",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// 10.6.15-MariaDB
`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
Package: "mariadb",
@@ -366,7 +360,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "rust-standard-library-linux",
FileGlob: "**/libstd-????????????????.so",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16))
`(?m)(\x00)clang LLVM \(rustc version (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
Package: "rust",
@@ -376,7 +370,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "rust-standard-library-macos",
FileGlob: "**/libstd-????????????????.dylib",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// c 1.48.0 (7eac88abb 2020-11-16)
`(?m)c (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
Package: "rust",
@@ -386,9 +380,9 @@ func DefaultClassifiers() []Classifier {
{
Class: "ruby-binary",
FileGlob: "**/ruby",
EvidenceMatcher: evidenceMatchers(
EvidenceMatcher: binutils.EvidenceMatchers(
rubyMatcher,
sharedLibraryLookup(
binutils.SharedLibraryLookup(
// try to find version information from libruby shared libraries
`^libruby\.so.*$`,
rubyMatcher),
@@ -400,12 +394,12 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-binary",
FileGlob: "**/erlexec",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
@@ -417,16 +411,16 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-alpine-binary",
FileGlob: "**/beam.smp",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// [NUL][NUL]26.1.2[NUL][NUL][NUL][NUL][NUL][NUL][NUL]NUL[NUL][NUL]Erlang/OTP
`\x00+(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)\x00+Erlang/OTP`,
),
@@ -438,12 +432,12 @@ func DefaultClassifiers() []Classifier {
{
Class: "erlang-library",
FileGlob: "**/liberts_internal.a",
EvidenceMatcher: evidenceMatchers(
FileContentsVersionMatcher(
EvidenceMatcher: binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
FileContentsVersionMatcher(
m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
),
@@ -455,7 +449,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "swipl-binary",
FileGlob: "**/swipl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
),
Package: "swipl",
@@ -465,7 +459,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "dart-binary",
FileGlob: "**/dart",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// MathAtan[NUL]2.12.4 (stable)
// "%s"[NUL]3.0.0 (stable)
// Dart,GC"[NUL]3.5.2 (stable)
@@ -479,7 +473,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-ghc-binary",
FileGlob: "**/ghc*",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00GHC (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "haskell/ghc",
@@ -489,7 +483,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-cabal-binary",
FileGlob: "**/cabal",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00Cabal-(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)-`,
),
Package: "haskell/cabal",
@@ -499,7 +493,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "haskell-stack-binary",
FileGlob: "**/stack",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)Version\s*(?P<version>[0-9]+\.[0-9]+\.[0-9]+),\s*Git`,
),
Package: "haskell/stack",
@@ -509,7 +503,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "consul-binary",
FileGlob: "**/consul",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// NOTE: This is brittle and may not work for past or future versions
`CONSUL_VERSION: (?P<version>\d+\.\d+\.\d+)`,
),
@@ -520,7 +514,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "nginx-binary",
FileGlob: "**/nginx",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1'
// [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part
`(?m)(\x00|\?)nginx version: [^\/]+\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`,
@@ -535,7 +529,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "bash-binary",
FileGlob: "**/bash",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// @(#)Bash version 5.2.15(1) release GNU
// @(#)Bash version 5.2.0(1) alpha GNU
// @(#)Bash version 5.2.0(1) beta GNU
@@ -549,7 +543,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "openssl-binary",
FileGlob: "**/openssl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]OpenSSL 3.1.4'
// [NUL]OpenSSL 1.1.1w'
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
@@ -561,7 +555,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "gcc-binary",
FileGlob: "**/gcc",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// GCC: \(GNU\) 12.3.0'
`GCC: \(GNU\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
@@ -572,7 +566,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "fluent-bit-binary",
FileGlob: "**/fluent-bit",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]3.0.2[NUL]%sFluent Bit
// [NUL]2.2.3[NUL]Fluent Bit
// [NUL]2.2.1[NUL][NUL][NUL]Fluent Bit
@@ -587,7 +581,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "wordpress-cli-binary",
FileGlob: "**/wp",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// wp-cli/wp-cli 2.9.0'
`(?m)wp-cli/wp-cli (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
@@ -598,7 +592,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "curl-binary",
FileGlob: "**/curl",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`curl/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
),
Package: "curl",
@@ -608,7 +602,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "lighttpd-binary",
FileGlob: "**/lighttpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00lighttpd/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "lighttpd",
@@ -618,7 +612,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "proftpd-binary",
FileGlob: "**/proftpd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00ProFTPD Version (?P<version>[0-9]+\.[0-9]+\.[0-9]+[a-z]?)\x00`,
),
Package: "proftpd",
@@ -628,7 +622,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "zstd-binary",
FileGlob: "**/zstd",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "zstd",
@@ -638,7 +632,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "xz-binary",
FileGlob: "**/xz",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00xz \(XZ Utils\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "xz",
@@ -648,7 +642,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "gzip-binary",
FileGlob: "**/gzip",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
),
Package: "gzip",
@@ -658,7 +652,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "sqlcipher-binary",
FileGlob: "**/sqlcipher",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`[^0-9]\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
),
Package: "sqlcipher",
@@ -668,7 +662,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "jq-binary",
FileGlob: "**/jq",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
`\x00(?P<version>[0-9]{1,3}\.[0-9]{1,3}(\.[0-9]+)?)\x00`,
),
Package: "jq",
@@ -678,7 +672,7 @@ func DefaultClassifiers() []Classifier {
{
Class: "chrome-binary",
FileGlob: "**/chrome",
EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]127.0.6533.119[NUL]Default
`\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\x00Default`,
),
@@ -689,18 +683,22 @@ func DefaultClassifiers() []Classifier {
}
}
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
// cpe string cannot be parsed into valid CPE Attributes
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
src := cpe.GeneratedSource
if len(source) > 0 {
src = source[0]
}
return []cpe.CPE{
cpe.Must(cpeString, src),
}
}
var libpythonMatcher = fileNameTemplateVersionMatcher(
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
pythonVersionTemplate,
)
var rubyMatcher = FileContentsVersionMatcher(
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
func mustPURL(purl string) packageurl.PackageURL {
p, err := packageurl.FromString(purl)
if err != nil {
panic(fmt.Sprintf("invalid PURL: %s", p))
}
return p
}
+18
View File
@@ -0,0 +1,18 @@
package binary
import "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
// Note: all generic utilities for catalogers have been moved to the internal/binutils package.
// Deprecated: This package is deprecated and will be removed in syft v2
type Classifier = binutils.Classifier
// Deprecated: This package is deprecated and will be removed in syft v2
type EvidenceMatcher = binutils.EvidenceMatcher
// Deprecated: This package is deprecated and will be removed in syft v2
func FileContentsVersionMatcher(
pattern string,
) EvidenceMatcher {
return binutils.FileContentsVersionMatcher(pattern, catalogerName)
}
@@ -1,12 +0,0 @@
name: php
offset: unknown
length: unknown
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
fileSha256: unknown
### byte snippet to follow ###
%s'
%s,%s
X-Powered-By: PHP/8.2.1
index pointer
PHP_VERSION
@@ -1,12 +0,0 @@
name: php-fpm
offset: unknown
length: unknown
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
fileSha256: unknown
### byte snippet to follow ###
%s'
%s,%s
X-Powered-By: PHP/8.2.1
index pointer
PHP_VERSION
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"bytes"
@@ -34,7 +34,7 @@ type Classifier struct {
// location. If the matcher returns a package, the file will be considered a candidate.
EvidenceMatcher EvidenceMatcher `json:"-"`
// Information below is used to specify the Package information when returned
// The information below is used to specify the Package information when returned
// Package is the name to use for the package
Package string `json:"package"`
@@ -72,16 +72,16 @@ func (cfg Classifier) MarshalJSON() ([]byte, error) {
}
// EvidenceMatcher is a function called to catalog Packages that match some sort of evidence
type EvidenceMatcher func(classifier Classifier, context matcherContext) ([]pkg.Package, error)
type EvidenceMatcher func(classifier Classifier, context MatcherContext) ([]pkg.Package, error)
type matcherContext struct {
resolver file.Resolver
location file.Location
getReader func(resolver matcherContext) (unionreader.UnionReader, error)
type MatcherContext struct {
Resolver file.Resolver
Location file.Location
GetReader func(resolver MatcherContext) (unionreader.UnionReader, error)
}
func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
func EvidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
for _, matcher := range matchers {
match, err := matcher(classifier, context)
if err != nil {
@@ -95,14 +95,26 @@ func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
}
}
func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
type ContextualEvidenceMatchers struct {
CatalogerName string
}
func (c ContextualEvidenceMatchers) FileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
return FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, c.CatalogerName)
}
func (c ContextualEvidenceMatchers) FileContentsVersionMatcher(pattern string) EvidenceMatcher {
return FileContentsVersionMatcher(pattern, c.CatalogerName)
}
func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerName string) EvidenceMatcher {
pat := regexp.MustCompile(fileNamePattern)
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
if !pat.MatchString(context.location.RealPath) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
if !pat.MatchString(context.Location.RealPath) {
return nil, nil
}
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.location.RealPath)
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.Location.RealPath)
// versions like 3.5 should not match any character, but explicit dot
for k, v := range filepathNamedGroupValues {
@@ -135,7 +147,7 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
return nil, fmt.Errorf("unable to match version: %w", err)
}
p := newClassifierPackage(classifier, context.location, matchMetadata)
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
if p == nil {
return nil, nil
}
@@ -144,9 +156,9 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
}
}
func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher {
pat := regexp.MustCompile(pattern)
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
contents, err := getReader(context)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
@@ -173,7 +185,7 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
}
}
p := newClassifierPackage(classifier, context.location, matchMetadata)
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
if p == nil {
return nil, nil
}
@@ -182,14 +194,14 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
}
}
// matchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
// MatchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
// anything that the matcher would otherwise return
func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
func MatchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
var nonMatchPatterns []*regexp.Regexp
for _, p := range contentPatternsToExclude {
nonMatchPatterns = append(nonMatchPatterns, regexp.MustCompile(p))
}
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
contents, err := getReader(context)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
@@ -205,9 +217,9 @@ func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string)
}
}
func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
pat := regexp.MustCompile(sharedLibraryPattern)
return func(classifier Classifier, context matcherContext) (packages []pkg.Package, _ error) {
return func(classifier Classifier, context MatcherContext) (packages []pkg.Package, _ error) {
libs, err := sharedLibraries(context)
if err != nil {
return nil, err
@@ -217,24 +229,24 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
continue
}
locations, err := context.resolver.FilesByGlob("**/" + lib)
locations, err := context.Resolver.FilesByGlob("**/" + lib)
if err != nil {
return nil, err
}
for _, libraryLocation := range locations {
newResolver := matcherContext{
resolver: context.resolver,
location: libraryLocation,
getReader: context.getReader,
newResolver := MatcherContext{
Resolver: context.Resolver,
Location: libraryLocation,
GetReader: context.GetReader,
}
newResolver.location = libraryLocation
newResolver.Location = libraryLocation
pkgs, err := sharedLibraryMatcher(classifier, newResolver)
if err != nil {
return nil, err
}
for _, p := range pkgs {
// set the source binary as the first location
locationSet := file.NewLocationSet(context.location)
locationSet := file.NewLocationSet(context.Location)
locationSet.Add(p.Locations.ToSlice()...)
p.Locations = locationSet
meta, _ := p.Metadata.(pkg.BinarySignature)
@@ -242,7 +254,7 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
Matches: append([]pkg.ClassifierMatch{
{
Classifier: classifier.Class,
Location: context.location,
Location: context.Location,
},
}, meta.Matches...),
}
@@ -254,19 +266,11 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
}
}
func mustPURL(purl string) packageurl.PackageURL {
p, err := packageurl.FromString(purl)
if err != nil {
panic(fmt.Sprintf("invalid PURL: %s", p))
func getReader(context MatcherContext) (unionreader.UnionReader, error) {
if context.GetReader != nil {
return context.GetReader(context)
}
return p
}
func getReader(context matcherContext) (unionreader.UnionReader, error) {
if context.getReader != nil {
return context.getReader(context)
}
reader, err := context.resolver.FileContentsByLocation(context.location) //nolint:gocritic
reader, err := context.Resolver.FileContentsByLocation(context.Location) //nolint:gocritic
if err != nil {
return nil, err
}
@@ -274,32 +278,20 @@ func getReader(context matcherContext) (unionreader.UnionReader, error) {
return unionreader.GetUnionReader(reader)
}
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
// cpe string cannot be parsed into valid CPE Attributes
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
src := cpe.GeneratedSource
if len(source) > 0 {
src = source[0]
}
return []cpe.CPE{
cpe.Must(cpeString, src),
}
}
// sharedLibraries returns a list of all shared libraries found within a binary, currently
// supporting: elf, macho, and windows pe
func sharedLibraries(context matcherContext) ([]string, error) {
func sharedLibraries(context MatcherContext) ([]string, error) {
contents, err := getReader(context)
if err != nil {
return nil, err
}
defer internal.CloseAndLogError(contents, context.location.RealPath)
defer internal.CloseAndLogError(contents, context.Location.RealPath)
e, _ := elf.NewFile(contents)
if e != nil {
symbols, err := e.ImportedLibraries()
if err != nil {
log.Debugf("unable to read elf binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read elf binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -311,7 +303,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
if m != nil {
symbols, err := m.ImportedLibraries()
if err != nil {
log.Debugf("unable to read macho binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read macho binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -323,7 +315,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
if p != nil {
symbols, err := p.ImportedLibraries()
if err != nil {
log.Debugf("unable to read pe binary at: %s -- %s", context.location.RealPath, err)
log.Debugf("unable to read pe binary at: %s -- %s", context.Location.RealPath, err)
}
return symbols, nil
}
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"reflect"
@@ -11,7 +11,7 @@ import (
var emptyPURL = packageurl.PackageURL{}
func newClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package {
func NewClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string, catalogerName string) *pkg.Package {
version, ok := matchMetadata["version"]
if !ok {
return nil
@@ -1,4 +1,4 @@
package binary
package binutils
import (
"bytes"
@@ -27,7 +27,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{},
},
cpes: nil,
@@ -38,7 +38,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
@@ -53,7 +53,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
@@ -70,7 +70,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version-parts.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`),
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`, "cataloger-name"),
CPEs: []cpe.CPE{},
},
cpes: nil,
@@ -84,7 +84,7 @@ func Test_ClassifierCPEs(t *testing.T) {
require.NoError(t, err)
require.Len(t, ls, 1)
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, matcherContext{resolver: resolver, location: ls[0]})
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, MatcherContext{Resolver: resolver, Location: ls[0]})
require.NoError(t, err)
require.Len(t, pkgs, 1)
@@ -113,7 +113,7 @@ func TestClassifier_MarshalJSON(t *testing.T) {
classifier: Classifier{
Class: "class",
FileGlob: "glob",
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
EvidenceMatcher: FileContentsVersionMatcher(".thing", "cataloger-name"),
Package: "pkg",
PURL: packageurl.PackageURL{
Type: "type",
@@ -165,12 +165,12 @@ func TestFileContentsVersionMatcher(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockGetContent := func(context matcherContext) (unionreader.UnionReader, error) {
mockGetContent := func(context MatcherContext) (unionreader.UnionReader, error) {
return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data)))
}
fn := FileContentsVersionMatcher(tt.pattern)
p, err := fn(Classifier{}, matcherContext{
getReader: mockGetContent,
fn := FileContentsVersionMatcher(tt.pattern, "cataloger-name")
p, err := fn(Classifier{}, MatcherContext{
GetReader: mockGetContent,
})
if err != nil {
@@ -7319,6 +7319,9 @@
"aco-product-labels-for-woocommerce": [
"cpe:2.3:a:acowebs:product_labels_for_woocommerce_\\(sale_badges\\):*:*:*:*:*:wordpress:*:*"
],
"aco-woo-dynamic-pricing": [
"cpe:2.3:a:acowebs:dynamic_pricing_with_discount_rules_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"actionwear-products-sync": [
"cpe:2.3:a:marcoingraiti:actionwear_products_sync:*:*:*:*:*:wordpress:*:*"
],
@@ -7473,6 +7476,10 @@
"admin-side-data-storage-for-contact-form-7": [
"cpe:2.3:a:zestard:admin_side_data_storage_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
],
"admin-site-enhancements": [
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:free:wordpress:*:*",
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:pro:wordpress:*:*"
],
"adminer": [
"cpe:2.3:a:adminer_login_project:adminer_login:*:*:*:*:*:wordpress:*:*"
],
@@ -8109,6 +8116,9 @@
"auto-hyperlink-urls": [
"cpe:2.3:a:auto-hyperlink_urls_project:auto-hyperlink_urls:*:*:*:*:*:wordpress:*:*"
],
"auto-iframe": [
"cpe:2.3:a:toolstack:auto_iframe:*:*:*:*:*:wordpress:*:*"
],
"auto-limit-posts-reloaded": [
"cpe:2.3:a:thefreewindows:auto_limit_posts_reloaded:*:*:*:*:*:wordpress:*:*"
],
@@ -9080,6 +9090,9 @@
"cf7-store-to-db-lite": [
"cpe:2.3:a:accesspressthemes:form_store_to_db:*:*:*:*:*:wordpress:*:*"
],
"cf7-submission-dom-tracking": [
"cpe:2.3:a:apasionados:submission_dom_tracking_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
],
"cf7-widget-elementor": [
"cpe:2.3:a:voidcoders:void_contact_form_7_widget_for_elementor_page_builder:*:*:*:*:*:wordpress:*:*"
],
@@ -9126,7 +9139,7 @@
"cpe:2.3:a:bluecoral:chat_bubble:*:*:*:*:*:wordpress:*:*"
],
"chatbot": [
"cpe:2.3:a:quantumcloud:ai_chatbot:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:quantumcloud:wpbot:*:*:*:*:free:wordpress:*:*"
],
"chatbot-chatgpt": [
"cpe:2.3:a:kognetics:kognetiks_chatbot:*:*:*:*:*:wordpress:*:*",
@@ -9266,12 +9279,19 @@
"cm-download-manager": [
"cpe:2.3:a:cminds:cm_download_manager:*:*:*:*:*:wordpress:*:*"
],
"cm-email-blacklist": [
"cpe:2.3:a:cminds:cm_e-mail_blacklist:*:*:*:*:*:wordpress:*:*"
],
"cm-on-demand-search-and-replace": [
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:cminds:cm_search_and_replace:*:*:*:*:*:wordpress:*:*"
],
"cm-pop-up-banners": [
"cpe:2.3:a:cminds:cm_popup:*:*:*:*:*:wordpress:*:*"
],
"cm-table-of-content": [
"cpe:2.3:a:cminds:cm_table_of_contents:*:*:*:*:*:wordpress:*:*"
],
"cm-video-lesson-manager": [
"cpe:2.3:a:creativemindssolutions:video_lessons_manager:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:creativemindssolutions:video_lessons_manager_pro:*:*:*:*:*:wordpress:*:*"
@@ -9711,7 +9731,7 @@
"cpe:2.3:a:marcorulicke:coru_lfmember:*:*:*:*:*:wordpress:*:*"
],
"cost-calculator-builder": [
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:free:wordpress:*:*"
],
"countdown-block": [
"cpe:2.3:a:wpdeveloper:countdown_block:*:*:*:*:*:wordpress:*:*"
@@ -10568,6 +10588,9 @@
"easy-newsletter-signups": [
"cpe:2.3:a:alphabpo:easy_newsletter_signups:*:*:*:*:*:wordpress:*:*"
],
"easy-notify-lite": [
"cpe:2.3:a:ghozylab:popup_builder:*:*:*:*:*:wordpress:*:*"
],
"easy-paypal-donation": [
"cpe:2.3:a:wpplugin:accept_donations_with_paypal:*:*:*:*:*:wordpress:*:*"
],
@@ -10610,6 +10633,9 @@
"easy-table": [
"cpe:2.3:a:easy_table_project:easy_table:*:*:*:*:*:wordpress:*:*"
],
"easy-table-of-contents": [
"cpe:2.3:a:magazine3:easy_table_of_contents:*:*:*:*:*:wordpress:*:*"
],
"easy-testimonial-manager": [
"cpe:2.3:a:easy_testimonial_manager_project:easy_testimonial_manager:*:*:*:*:*:wordpress:*:*"
],
@@ -12404,7 +12430,8 @@
"cpe:2.3:a:idxbroker:impress_for_idx_broker:*:*:*:*:platinum:wordpress:*:*"
],
"if-so": [
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:if-so:if-so:*:*:*:*:*:wordpress:*:*"
],
"ifeature-slider": [
"cpe:2.3:a:cyberchimps:ifeature_slider:*:*:*:*:*:wordpress:*:*"
@@ -12494,6 +12521,9 @@
"image-vertical-reel-scroll-slideshow": [
"cpe:2.3:a:gopiplus:image_vertical_reel_scroll_slideshow:*:*:*:*:*:wordpress:*:*"
],
"image-widget": [
"cpe:2.3:a:stellarwp:image_widget:*:*:*:*:*:wordpress:*:*"
],
"imageboss": [
"cpe:2.3:a:imageboss:imageboss:*:*:*:*:*:wordpress:*:*"
],
@@ -12782,6 +12812,9 @@
"jetformbuilder": [
"cpe:2.3:a:crocoblock:jetformbuilder:*:*:*:*:*:wordpress:*:*"
],
"jetpack": [
"cpe:2.3:a:automattic:jetpack:*:*:*:*:*:wordpress:*:*"
],
"jetwidgets-for-elementor": [
"cpe:2.3:a:crocoblock:jetwidgets_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -13723,6 +13756,9 @@
"message-ticker": [
"cpe:2.3:a:gopiplus:message_ticker:*:*:*:*:*:wordpress:*:*"
],
"meta-box": [
"cpe:2.3:a:metabox:meta_box:*:*:*:*:*:wordpress:*:*"
],
"meta-store-elements": [
"cpe:2.3:a:mysticalthemes:meta_store_elements:*:*:*:*:*:wordpress:*:*"
],
@@ -13984,6 +14020,9 @@
"cpe:2.3:a:codepeople:music_store:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:musicstore:music_store:*:*:*:*:*:wordpress:*:*"
],
"muslim-prayer-time-bd": [
"cpe:2.3:a:realwebcare:muslim_prayer_time_bd:*:*:*:*:*:wordpress:*:*"
],
"mw-wp-form": [
"cpe:2.3:a:web-soudan:mw_wp_form:*:*:*:*:*:wordpress:*:*"
],
@@ -14426,6 +14465,9 @@
"opening-hours": [
"cpe:2.3:a:designextreme:we\\'re_open\\!:*:*:*:*:*:wordpress:*:*"
],
"openpgp-form-encryption": [
"cpe:2.3:a:arnesonium:openpgp_form_encryption:*:*:*:*:*:wordpress:*:*"
],
"ops-robots-txt": [
"cpe:2.3:a:ops-robots-txt_project:ops-robots-txt:*:*:*:*:*:wordpress:*:*"
],
@@ -14769,6 +14811,9 @@
"cpe:2.3:a:greentreelabs:gallery_photoblocks:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpchill:gallery_photoblocks:*:*:*:*:*:wordpress:*:*"
],
"photoshow": [
"cpe:2.3:a:codepeople:smart_image_gallery:*:*:*:*:*:wordpress:*:*"
],
"photospace": [
"cpe:2.3:a:photospace_gallery_project:photospace_gallery:*:*:*:*:*:wordpress:*:*"
],
@@ -15236,6 +15281,9 @@
"product-designer": [
"cpe:2.3:a:pickplugins:product_designer:*:*:*:*:*:wordpress:*:*"
],
"product-enquiry-for-woocommerce": [
"cpe:2.3:a:wisdmlabs:product_enquiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"product-expiry-for-woocommerce": [
"cpe:2.3:a:webcodingplace:product_expiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -15460,6 +15508,7 @@
"cpe:2.3:a:softlabbd:radio_player:*:*:*:*:*:wordpress:*:*"
],
"rafflepress": [
"cpe:2.3:a:rafflepress:rafflepress:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:seedprod:rafflepress:*:*:*:*:*:wordpress:*:*"
],
"random-banner": [
@@ -16306,6 +16355,9 @@
"simple-ajax-chat": [
"cpe:2.3:a:plugin-planet:simple_ajax_chat:*:*:*:*:*:wordpress:*:*"
],
"simple-al-slider": [
"cpe:2.3:a:alexdtn:simple_al_slider:*:*:*:*:*:wordpress:*:*"
],
"simple-author-box": [
"cpe:2.3:a:webfactoryltd:simple_author_box:*:*:*:*:*:wordpress:*:*"
],
@@ -16322,6 +16374,9 @@
"simple-bitcoin-faucets": [
"cpe:2.3:a:simple_bitcoin_faucets_project:simple_bitcoin_faucets:*:*:*:*:*:wordpress:*:*"
],
"simple-buttons-creator": [
"cpe:2.3:a:robbychen:simple_buttons_creator:*:*:*:*:*:wordpress:*:*"
],
"simple-calendar-for-elementor": [
"cpe:2.3:a:migaweb:simple_calendar_for_elementor:*:*:*:*:*:wordpress:*:*"
],
@@ -16494,6 +16549,9 @@
"simple-site-verify": [
"cpe:2.3:a:idoweb:simple_site_verify:*:*:*:*:*:wordpress:*:*"
],
"simple-sitemap": [
"cpe:2.3:a:wpgoplugins:simple_sitemap:*:*:*:*:free:wordpress:*:*"
],
"simple-slug-translate": [
"cpe:2.3:a:simple_slug_translate_project:simple_slug_translate:*:*:*:*:*:wordpress:*:*"
],
@@ -17233,12 +17291,18 @@
"surveys": [
"cpe:2.3:a:surveys_project:surveys:*:*:*:*:*:wordpress:*:*"
],
"svg-block": [
"cpe:2.3:a:boldblocks:svg_block:*:*:*:*:*:wordpress:*:*"
],
"svg-support": [
"cpe:2.3:a:benbodhi:svg_support:*:*:*:*:*:wordpress:*:*"
],
"svg-vector-icon-plugin": [
"cpe:2.3:a:wp_svg_icons_project:wp_svg_icons:*:*:*:*:*:wordpress:*:*"
],
"svgator": [
"cpe:2.3:a:svgator:svgator:*:*:*:*:*:wordpress:*:*"
],
"svgmagic": [
"cpe:2.3:a:andibauer:svgmagic:*:*:*:*:*:wordpress:*:*"
],
@@ -17684,6 +17748,9 @@
"tripetto": [
"cpe:2.3:a:tripetto:tripetto:*:*:*:*:*:wordpress:*:*"
],
"truebooker-appointment-booking": [
"cpe:2.3:a:themetechmount:truebooker:*:*:*:*:*:wordpress:*:*"
],
"ts-webfonts-for-conoha": [
"cpe:2.3:a:gmo:typesquare_webfonts_for_conoha:*:*:*:*:*:wordpress:*:*"
],
@@ -17914,6 +17981,9 @@
"underconstruction": [
"cpe:2.3:a:underconstruction_project:underconstruction:*:*:*:*:*:wordpress:*:*"
],
"ungallery": [
"cpe:2.3:a:mmond:ungallery:*:*:*:*:*:wordpress:*:*"
],
"uninstall": [
"cpe:2.3:a:wordpress_uninstall_project:wordpress_uninstall:*:*:*:*:*:wordpress:*:*"
],
@@ -18697,6 +18767,9 @@
"woo-tranzila-gateway": [
"cpe:2.3:a:antonbond:woocommerce_tranzila_payment_gateway:*:*:*:*:*:wordpress:*:*"
],
"woo-ups-pickup": [
"cpe:2.3:a:ashamil:opsi_israel_domestic_shipments:*:*:*:*:*:wordpress:*:*"
],
"woo-variation-gallery": [
"cpe:2.3:a:getwooplugins:additional_variation_images_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -19971,6 +20044,9 @@
"wp-seo-redirect-301": [
"cpe:2.3:a:wp_seo_redirect_301_project:wp_seo_redirect_301:*:*:*:*:*:wordpress:*:*"
],
"wp-seo-structured-data-schema": [
"cpe:2.3:a:wpsemplugins:wp_seo_structured_data_schema:*:*:*:*:free:wordpress:*:*"
],
"wp-seo-tags": [
"cpe:2.3:a:wp_seo_tags_project:wp_seo_tags:*:*:*:*:*:wordpress:*:*"
],
@@ -20284,6 +20360,9 @@
"wp-webauthn": [
"cpe:2.3:a:axton:wp-webauthn:*:*:*:*:*:wordpress:*:*"
],
"wp-webinarsystem": [
"cpe:2.3:a:webinarpress:webinarpress:*:*:*:*:*:wordpress:*:*"
],
"wp-whatsapp": [
"cpe:2.3:a:ninjateam:wp_chat_app:*:*:*:*:*:wordpress:*:*"
],
@@ -20305,6 +20384,9 @@
"wp-worthy": [
"cpe:2.3:a:tiggerswelt:worthy:*:*:*:*:*:wordpress:*:*"
],
"wp-yadisk-files": [
"cpe:2.3:a:antongorodezkiy:yadisk_files:*:*:*:*:*:wordpress:*:*"
],
"wp-yelp-review-slider": [
"cpe:2.3:a:ljapps:wp_yelp_review_slider:*:*:*:*:*:wordpress:*:*"
],
@@ -20669,6 +20751,10 @@
"yith-maintenance-mode": [
"cpe:2.3:a:yithemes:yith_maintenance_mode:*:*:*:*:*:wordpress:*:*"
],
"yith-woocommerce-ajax-search": [
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:free:wordpress:*:*"
],
"yith-woocommerce-gift-cards": [
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:free:wordpress:*:*"
@@ -27,6 +27,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/directorysource"
"github.com/anchore/syft/syft/source/filesource"
"github.com/anchore/syft/syft/source/stereoscopesource"
)
@@ -96,6 +97,10 @@ func (p *CatalogTester) WithContext(ctx context.Context) *CatalogTester {
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
t.Helper()
if path == "" {
return p
}
s, err := directorysource.NewFromPath(path)
require.NoError(t, err)
@@ -106,9 +111,25 @@ func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester
return p
}
func (p *CatalogTester) FromFileSource(t *testing.T, path string) *CatalogTester {
t.Helper()
s, err := filesource.NewFromPath(path)
require.NoError(t, err)
resolver, err := s.FileResolver(source.AllLayersScope)
require.NoError(t, err)
p.resolver = resolver
return p
}
func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester {
t.Helper()
if path == "" {
return p
}
fixture, err := os.Open(path)
require.NoError(t, err)
@@ -157,6 +178,11 @@ func (p *CatalogTester) WithResolver(r file.Resolver) *CatalogTester {
func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *CatalogTester {
t.Helper()
if fixtureName == "" {
return p
}
img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName)
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
@@ -327,6 +353,11 @@ func TestCataloger(t *testing.T, fixtureDir string, cataloger pkg.Cataloger, exp
NewCatalogTester().FromDirectory(t, fixtureDir).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}
func TestCatalogerFromFileSource(t *testing.T, fixturePath string, cataloger pkg.Cataloger, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
NewCatalogTester().FromFileSource(t, fixturePath).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}
func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Parser, env *generic.Environment, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
+108
View File
@@ -215,3 +215,111 @@ func TestJvmDistributionCataloger(t *testing.T) {
}
}
func TestJvmDistributionCatalogerFromFile(t *testing.T) {
cases := []struct {
name string
fixture string
expected pkg.Package
}{
{
name: "valid 1.8.0",
fixture: "test-fixtures/jvm-installs/oracle-jdk-se-8/usr/lib/jvm/jdk-1.8-oracle-x64/release",
expected: pkg.Package{
Name: "jdk",
Version: "1.8.0_411-b25",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jre:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jdk:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
},
PURL: "pkg:generic/oracle/jdk@1.8.0_411-b25",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
JavaRuntimeVersion: "1.8.0_411-b25",
JavaVersion: "1.8.0_411",
OsArch: "amd64",
OsName: "Linux",
OsVersion: "2.6",
Source: ".:git:71ec2089cf8c+",
BuildType: "commercial",
},
Files: []string{
"/release",
},
},
},
},
{
name: "valid post-jep223",
fixture: "test-fixtures/jvm-installs/valid-post-jep223/jvm/openjdk/release",
expected: pkg.Package{
Name: "openjdk",
Version: "21.0.4+7-LTS",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:oracle:openjdk:21.0.4:*:*:*:*:*:*:*", cpe.DeclaredSource)},
PURL: "pkg:generic/oracle/openjdk@21.0.4%2B7-LTS?repository_url=https%3A%2F%2Fgithub.com%2Fadoptium%2Fjdk21u.git",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
Implementor: "Eclipse Adoptium",
ImplementorVersion: "Temurin-21.0.4+7",
JavaRuntimeVersion: "21.0.4+7-LTS",
JavaVersion: "21.0.4",
JavaVersionDate: "2024-07-16",
Libc: "gnu",
Modules: []string{
"java.base", "java.compiler", "java.datatransfer", "java.xml", "java.prefs",
"java.desktop", "java.instrument", "java.logging", "java.management",
"java.security.sasl", "java.naming", "java.rmi", "java.management.rmi",
"java.net.http", "java.scripting", "java.security.jgss",
"java.transaction.xa", "java.sql", "java.sql.rowset", "java.xml.crypto", "java.se",
"java.smartcardio", "jdk.accessibility", "jdk.internal.jvmstat", "jdk.attach",
"jdk.charsets", "jdk.internal.opt", "jdk.zipfs", "jdk.compiler", "jdk.crypto.ec",
"jdk.crypto.cryptoki", "jdk.dynalink", "jdk.internal.ed", "jdk.editpad", "jdk.hotspot.agent",
"jdk.httpserver", "jdk.incubator.vector", "jdk.internal.le", "jdk.internal.vm.ci",
"jdk.internal.vm.compiler", "jdk.internal.vm.compiler.management", "jdk.jartool",
"jdk.javadoc", "jdk.jcmd", "jdk.management", "jdk.management.agent", "jdk.jconsole",
"jdk.jdeps", "jdk.jdwp.agent", "jdk.jdi", "jdk.jfr", "jdk.jlink", "jdk.jpackage", "jdk.jshell",
"jdk.jsobject", "jdk.jstatd", "jdk.localedata", "jdk.management.jfr", "jdk.naming.dns",
"jdk.naming.rmi", "jdk.net", "jdk.nio.mapmode", "jdk.random", "jdk.sctp", "jdk.security.auth",
"jdk.security.jgss", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom",
},
OsArch: "aarch64",
OsName: "Linux",
Source: ".:git:13710926b798",
BuildSource: "git:1271f10a26c47e1489a814dd2731f936a588d621",
BuildSourceRepo: "https://github.com/adoptium/temurin-build.git",
SourceRepo: "https://github.com/adoptium/jdk21u.git",
FullVersion: "21.0.4+7-LTS",
SemanticVersion: "21.0.4+7",
BuildInfo: "OS: Linux Version: 5.4.0-150-generic",
JvmVariant: "Hotspot",
JvmVersion: "21.0.4+7-LTS",
ImageType: "JDK",
},
Files: []string{
"/release",
},
},
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
p := tt.expected
p.SetID()
pkgtest.TestCatalogerFromFileSource(t, tt.fixture, NewJvmDistributionCataloger(), []pkg.Package{p}, nil)
})
}
}
@@ -16,6 +16,7 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/cyclonedxjson"
@@ -508,7 +509,7 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, relationships []artifac
}
// fetchPkgs provides the packages available in a UnionReader.
func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package, []artifact.Relationship) {
func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
var relationships []artifact.Relationship
imageFormats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
@@ -518,8 +519,9 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package,
readers, err := unionreader.GetReaders(reader)
if err != nil {
log.Debugf("failed to open the java native-image binary: %v", err)
return nil, nil
return nil, nil, nil
}
var unknowns error
for _, r := range readers {
for _, makeNativeImage := range imageFormats {
ni, err := makeNativeImage(filename, r)
@@ -532,13 +534,14 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package,
newPkgs, newRelationships, err := ni.fetchPkgs()
if err != nil {
log.Tracef("unable to extract SBOM from possible java native-image %s: %v", filename, err)
unknowns = unknown.Join(unknowns, fmt.Errorf("unable to extract SBOM from possible java native-image %s: %w", filename, err))
continue
}
pkgs = append(pkgs, newPkgs...)
relationships = append(relationships, newRelationships...)
}
}
return pkgs, relationships
return pkgs, relationships, unknowns
}
// Catalog attempts to find any native image executables reachable from a resolver.
@@ -549,17 +552,18 @@ func (c *nativeImageCataloger) Catalog(_ context.Context, resolver file.Resolver
if err != nil {
return pkgs, nil, fmt.Errorf("failed to find binaries by mime types: %w", err)
}
var errs error
for _, location := range fileMatches {
newPkgs, newRelationships, err := processLocation(location, resolver)
if err != nil {
return nil, nil, err
errs = unknown.Append(errs, location, err)
continue
}
pkgs = append(pkgs, newPkgs...)
relationships = append(relationships, newRelationships...)
}
return pkgs, relationships, nil
return pkgs, relationships, errs
}
func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
@@ -574,6 +578,6 @@ func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Pack
if err != nil {
return nil, nil, err
}
pkgs, relationships := fetchPkgs(reader, location.RealPath)
return pkgs, relationships, nil
pkgs, relationships, err := fetchPkgs(reader, location.RealPath)
return pkgs, relationships, err
}
+4 -1
View File
@@ -88,7 +88,10 @@ func parseJVMRelease(_ context.Context, resolver file.Resolver, _ *generic.Envir
installDir := path.Dir(reader.Path())
files, hasJdk := findJvmFiles(resolver, installDir)
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, reader.Path(), ri.ImageType, hasJdk)
// the reason we use the reference to get the real path is in cases where the file cataloger is involved
// (thus the real path is not available except for in the original file reference). This is important
// since the path is critical for distinguishing between different JVM vendors.
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, string(reader.Reference().RealPath), ri.ImageType, hasJdk)
p := pkg.Package{
Name: product,
+4 -6
View File
@@ -3,16 +3,14 @@ package nix
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
_ "modernc.org/sqlite"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCataloger_Image(t *testing.T) {
@@ -0,0 +1,240 @@
package php
import (
"context"
"fmt"
"path"
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
type interpreterCataloger struct {
name string
extensionsGlob string
interpreterClassifiers []binutils.Classifier
}
// NewInterpreterCataloger returns a new cataloger for PHP interpreters (php and php-fpm) as well as any installed C extensions.
func NewInterpreterCataloger() pkg.Cataloger { //nolint:funlen
name := "php-interpreter-cataloger"
m := binutils.ContextualEvidenceMatchers{CatalogerName: name}
return interpreterCataloger{
name: name,
// example matches:
// - as found in php-fpm docker library images: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so
// - as found in alpine images: /usr/lib/php83/modules/bcmath.so
extensionsGlob: "**/php*/**/*.so",
interpreterClassifiers: []binutils.Classifier{
{
Class: "php-cli-binary",
FileGlob: "**/php*",
EvidenceMatcher: m.FileNameTemplateVersionMatcher(
`(.*/|^)php[0-9]*$`,
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-cli",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php-cli",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
{
Class: "php-fpm-binary",
FileGlob: "**/php-fpm*",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "php-fpm",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php-fpm",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
{
Class: "php-apache-binary",
FileGlob: "**/apache*/**/libphp*.so",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
Package: "libphp",
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: "php",
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "php",
Product: "php",
},
Source: cpe.NVDDictionaryLookupSource,
},
},
},
},
}
}
func (p interpreterCataloger) Name() string {
return p.name
}
func (p interpreterCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
interpreterPkgs, intErrs := p.catalogInterpreters(resolver)
extensionPkgs, extErrs := p.catalogExtensions(resolver)
// TODO: a future iteration of this cataloger could be to read all php.ini / php/conf.d/*.ini files and indicate which extensions are enabled
// and attempt to resolve the extension_dir. This can be tricky as it is a #define in the php source code and not always available
// in configuration. For the meantime we report all extensions present
// create a relationship for each interpreter package to the extensions
var relationships []artifact.Relationship
for _, interpreter := range interpreterPkgs {
for _, extension := range extensionPkgs {
relationships = append(relationships, artifact.Relationship{
From: extension,
To: interpreter,
Type: artifact.DependencyOfRelationship,
})
}
}
var allPkgs []pkg.Package
allPkgs = append(allPkgs, interpreterPkgs...)
allPkgs = append(allPkgs, extensionPkgs...)
return allPkgs, relationships, unknown.Join(intErrs, extErrs)
}
func (p interpreterCataloger) catalogInterpreters(resolver file.Resolver) ([]pkg.Package, error) {
var errs error
var packages []pkg.Package
for _, cls := range p.interpreterClassifiers {
locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil {
// convert any file.Resolver path errors to unknowns with locations
errs = unknown.Join(errs, unknown.ProcessPathErrors(err))
continue
}
for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
errs = unknown.Append(errs, location, err)
continue
}
packages = append(packages, pkgs...)
}
}
return packages, errs
}
func (p interpreterCataloger) catalogExtensions(resolver file.Resolver) ([]pkg.Package, error) {
locations, err := resolver.FilesByGlob(p.extensionsGlob)
if err != nil {
// convert any file.Resolver path errors to unknowns with locations
return nil, unknown.ProcessPathErrors(err)
}
var packages []pkg.Package
var errs error
for _, location := range locations {
pkgs, err := p.catalogExtension(resolver, location)
if err != nil {
errs = unknown.Append(errs, location, err)
continue
}
packages = append(packages, pkgs...)
}
return packages, errs
}
func (p interpreterCataloger) catalogExtension(resolver file.Resolver, location file.Location) ([]pkg.Package, error) {
reader, err := resolver.FileContentsByLocation(location)
defer internal.CloseAndLogError(reader, location.RealPath)
if err != nil {
return nil, unknown.ProcessPathErrors(err)
}
name, cls := p.getClassifier(location.RealPath)
if name == "" || cls == nil {
return nil, nil
}
pkgs, err := cls.EvidenceMatcher(*cls, binutils.MatcherContext{Resolver: resolver, Location: location})
if err != nil {
return nil, unknown.New(location, err)
}
return pkgs, err
}
func (p interpreterCataloger) getClassifier(realPath string) (string, *binutils.Classifier) {
if !strings.HasSuffix(realPath, ".so") {
return "", nil
}
base := path.Base(realPath)
name := strings.TrimSuffix(base, ".so")
var match string
switch name {
case "mysqli":
match = `(mysqlnd|mysqli)?\s*\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`
case "opcache":
match = `(?m)\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+`
case "zip":
match = `\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+Zip`
default:
match = fmt.Sprintf(`(?m)(\x00+%s)?\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`, name)
}
return name, &binutils.Classifier{
Class: fmt.Sprintf("php-ext-%s-binary", name),
EvidenceMatcher: binutils.FileContentsVersionMatcher(match, p.name),
Package: name,
PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric,
Name: name,
// the version will be filled in dynamically
},
CPEs: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: fmt.Sprintf("php-%s", name),
Product: fmt.Sprintf("php-%s", name),
},
Source: cpe.GeneratedSource,
},
},
}
}
@@ -0,0 +1,149 @@
package php
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_InterpreterCataloger(t *testing.T) {
tests := []struct {
name string
fixture string
expectedPkgs []string
expectedRels []string
}{
{
name: "native installation with extensions",
fixture: "image-extensions",
expectedPkgs: []string{
// interpreters
"php-cli @ 8.3.21 (/usr/local/bin/php)",
"php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
// extensions
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so)",
},
expectedRels: []string{
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
},
},
{
name: "apache installation with libphp and extensions",
fixture: "image-apache",
expectedPkgs: []string{
// interpreters
"libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
// extensions
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so)",
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so)",
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so)",
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so)",
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so)",
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so)",
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so)",
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so)",
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so)",
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so)",
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so)",
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so)",
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so)",
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so)",
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so)",
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so)",
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so)",
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so)",
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so)",
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so)",
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so)",
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so)",
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so)",
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so)",
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so)",
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so)",
},
expectedRels: []string{
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewInterpreterCataloger()
pkgtest.NewCatalogTester().
WithImageResolver(t, tt.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
//Expects(tt.expected, nil).
ExpectsPackageStrings(tt.expectedPkgs).
ExpectsRelationshipStrings(tt.expectedRels).
TestCataloger(t, c)
})
}
}
@@ -0,0 +1,11 @@
FROM --platform=linux/amd64 httpd:2.4.63-bookworm AS builder
RUN apt update -y && apt install -y libapache2-mod-php php8.2-memcache php8.2-memcache php8.2-xml php8.2-mysqli php8.2-opcache
FROM busybox:latest
# phplib.so
COPY --from=builder /usr/lib/apache2/ /usr/lib/apache2/
# php extensions
COPY --from=builder /usr/lib/php/ /usr/lib/php/
@@ -0,0 +1,105 @@
# source https://github.com/nextcloud/docker/blob/master/30/fpm-alpine/Dockerfile#L1
FROM --platform=linux/amd64 php:8.3-fpm-alpine3.21 AS builder
# entrypoint.sh and cron.sh dependencies
RUN set -ex; \
\
apk add --no-cache \
imagemagick \
imagemagick-pdf \
imagemagick-jpeg \
imagemagick-raw \
imagemagick-tiff \
imagemagick-heic \
imagemagick-webp \
imagemagick-svg \
rsync \
; \
\
rm /var/spool/cron/crontabs/root; \
echo '*/5 * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data
# install the PHP extensions we need
# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html
RUN set -ex; \
\
apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
autoconf \
freetype-dev \
gmp-dev \
icu-dev \
imagemagick-dev \
libevent-dev \
libjpeg-turbo-dev \
libmcrypt-dev \
libmemcached-dev \
libpng-dev \
libwebp-dev \
libxml2-dev \
libzip-dev \
openldap-dev \
pcre-dev \
postgresql-dev \
; \
\
docker-php-ext-configure ftp --with-openssl-dir=/usr; \
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \
docker-php-ext-configure ldap; \
docker-php-ext-install -j "$(nproc)" \
bcmath \
exif \
ftp \
gd \
gmp \
intl \
ldap \
opcache \
pcntl \
pdo_mysql \
pdo_pgsql \
sysvsem \
zip \
; \
\
# pecl will claim success even if one install fails, so we need to perform each install separately
pecl install APCu-5.1.24; \
pecl install igbinary-3.2.16; \
pecl install imagick-3.8.0; \
pecl install memcached-3.3.0 \
--configureoptions 'enable-memcached-igbinary="yes"'; \
pecl install redis-6.2.0 \
--configureoptions 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"'; \
\
docker-php-ext-enable \
apcu \
igbinary \
imagick \
memcached \
redis \
; \
rm -r /tmp/pear; \
\
runDeps="$( \
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)"; \
apk add --no-network --virtual .nextcloud-phpext-rundeps $runDeps; \
apk del --no-network .build-deps
FROM busybox:latest
# interpreters + process manager
COPY --from=builder /usr/local/sbin/php-fpm /usr/local/sbin/php-fpm
COPY --from=builder /usr/local/bin/php /usr/local/bin/php
# extensions
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
# configs
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=builder /usr/local/etc/php-fpm.d /usr/local/etc/php-fpm.d
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
+1 -1
View File
@@ -758,7 +758,7 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
// Note: we'll only see new relationships, so any relationship where there is at least one new player (in FROM or TO)
"blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
"python-editor @ 1.0.4 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
"readchar @ 4.2.0 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
"readchar @ 4.2.1 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
"soupsieve @ 2.3 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] beautifulsoup4 @ 4.10.0 (/app/project1/venv/lib/python3.9/site-packages)",
// project 2 virtual env
@@ -13,10 +13,10 @@ RUN python3.9 -m pip install click==8.0.3 beautifulsoup4==4.9.3 soupsieve==2.2.1
RUN python3.9 -m pip install six==1.16.0 wcwidth==0.2.13 blessed==1.20.0 python-editor==1.0.4 # total dependencies for inquirer in project1 (which is linked)
RUN python3.9 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
RUN python3.8 -m pip install readchar==4.2.1
RUN python3.8 -m pip install click==8.0.2 beautifulsoup4==4.9.2 soupsieve==2.2.0 requests==2.25.0
RUN python3.8 -m pip install runs==1.2.2 xmod==1.8.1 # partial dependencies for inquirer in project2 (which is a red herring)
RUN python3.8 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
RUN python3.8 -m pip install readchar==4.2.0
# create directories for the two projects
RUN mkdir -p /app/project1 /app/project2
+21 -13
View File
@@ -5,14 +5,13 @@ import (
"errors"
"testing"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
_ "modernc.org/sqlite"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_DBCataloger(t *testing.T) {
@@ -29,14 +28,15 @@ func Test_DBCataloger(t *testing.T) {
FoundBy: "rpm-db-cataloger",
PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
Metadata: pkg.RpmDBEntry{
Name: "basesystem",
Version: "11",
Arch: "noarch",
Release: "13.el9",
SourceRpm: "basesystem-11-13.el9.src.rpm",
Size: 0,
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{"basesystem"},
Name: "basesystem",
Version: "11",
Arch: "noarch",
Release: "13.el9",
SourceRpm: "basesystem-11-13.el9.src.rpm",
Size: 0,
Vendor: "Rocky Enterprise Software Foundation",
Signatures: mustParseSignatures(t, "RSA/SHA256, Wed May 11 11:12:32 2022, Key ID 702d426d350d275d"),
Provides: []string{"basesystem"},
Requires: []string{
"filesystem",
"rpmlib(CompressedFileNames)",
@@ -65,6 +65,7 @@ func Test_DBCataloger(t *testing.T) {
Release: "6.el9_1",
SourceRpm: "bash-5.1.8-6.el9_1.src.rpm",
Size: 7738634,
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon Jan 23 22:49:22 2023, Key ID 702d426d350d275d"),
ModularityLabel: strRef(""),
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{
@@ -117,6 +118,7 @@ func Test_DBCataloger(t *testing.T) {
Release: "2.el9",
SourceRpm: "filesystem-3.16-2.el9.src.rpm",
Size: 106,
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"),
ModularityLabel: strRef(""),
Vendor: "Rocky Enterprise Software Foundation",
Provides: []string{
@@ -180,7 +182,7 @@ func Test_DBCataloger(t *testing.T) {
pkgtest.NewCatalogTester().
WithImageResolver(t, "image-minimal").
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
WithCompareOptions(cmpopts.IgnoreFields(pkg.RpmDBEntry{}, "Files")). // this is rather long... ano not the point of the test
Expects(expectedPackages, expectedRelationships).
TestCataloger(t, NewDBCataloger())
@@ -334,3 +336,9 @@ func Test_denySelfReferences(t *testing.T) {
})
}
}
func mustParseSignatures(t testing.TB, sigs ...string) []pkg.RpmSignature {
signatures, err := parseSignatures(sigs...)
require.NoError(t, err)
return signatures
}
+171 -11
View File
@@ -1,11 +1,16 @@
package redhat
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"sort"
"strconv"
"time"
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
rpmdb "github.com/anchore/go-rpmdb/pkg"
"github.com/sassoftware/go-rpmutils"
"github.com/anchore/syft/internal/log"
@@ -15,6 +20,42 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type pgpSig struct {
_ [3]byte
Date int32
KeyID [8]byte
PubKeyAlgo uint8
HashAlgo uint8
}
type textSig struct {
_ [2]byte
PubKeyAlgo uint8
HashAlgo uint8
_ [4]byte
Date int32
_ [4]byte
KeyID [8]byte
}
type pgp4Sig struct {
_ [2]byte
PubKeyAlgo uint8
HashAlgo uint8
_ [17]byte
KeyID [8]byte
_ [2]byte
Date int32
}
var pubKeyLookup = map[uint8]string{
0x01: "RSA",
}
var hashLookup = map[uint8]string{
0x02: "SHA1",
0x08: "SHA256",
}
// parseRpmArchive parses a single RPM
func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
rpm, err := rpmutils.ReadRpm(reader)
@@ -44,21 +85,140 @@ func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
files, err := rpm.Header.GetFiles()
logRpmArchiveErr(reader.Location, "files", err)
rsa, err := rpm.Header.GetBytes(rpmutils.SIG_RSA)
logRpmArchiveErr(reader.Location, "rsa signature", err)
pgp, err := rpm.Header.GetBytes(rpmutils.SIG_PGP)
logRpmArchiveErr(reader.Location, "pgp signature", err)
var allSigs [][]byte
allSigs = append(allSigs, rsa)
allSigs = append(allSigs, pgp)
sigs, err := parseSignatureHeaders(allSigs)
logRpmArchiveErr(reader.Location, "signature", err)
metadata := pkg.RpmArchive{
Name: nevra.Name,
Version: nevra.Version,
Epoch: parseEpoch(nevra.Epoch),
Arch: nevra.Arch,
Release: nevra.Release,
SourceRpm: sourceRpm,
Vendor: vendor,
Size: int(size),
Files: mapFiles(files, digestAlgorithm),
Name: nevra.Name,
Version: nevra.Version,
Epoch: parseEpoch(nevra.Epoch),
Arch: nevra.Arch,
Release: nevra.Release,
SourceRpm: sourceRpm,
Signatures: sigs,
Vendor: vendor,
Size: int(size),
Files: mapFiles(files, digestAlgorithm),
}
return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil
}
func parseSignatureHeaders(data [][]byte) ([]pkg.RpmSignature, error) {
sigMap := make(map[string]pkg.RpmSignature)
var keys []string
for _, sig := range data {
if len(sig) == 0 {
continue
}
s, err := parsePGP(sig)
if err != nil {
log.WithFields("error", err).Trace("unable to parse RPM archive signature")
return nil, err
}
k := s.String()
if _, ok := sigMap[k]; ok {
// if we have a duplicate signature, just skip it
continue
}
sigMap[k] = *s
keys = append(keys, k)
}
var signatures []pkg.RpmSignature
sort.Strings(keys)
for _, k := range keys {
signatures = append(signatures, sigMap[k])
}
return signatures, nil
}
func parsePGP(data []byte) (*pkg.RpmSignature, error) {
var tag, signatureType, version uint8
r := bytes.NewReader(data)
err := binary.Read(r, binary.BigEndian, &tag)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &signatureType)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &version)
if err != nil {
return nil, err
}
switch signatureType {
case 0x01:
switch version {
case 0x1c:
sig := textSig{}
err = binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
}
return &pkg.RpmSignature{
PublicKeyAlgorithm: pubKeyLookup[sig.PubKeyAlgo],
HashAlgorithm: hashLookup[sig.HashAlgo],
Created: time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006"),
IssuerKeyID: fmt.Sprintf("%x", sig.KeyID),
}, nil
default:
return decodePGPSig(version, r)
}
case 0x02:
return decodePGPSig(version, r)
}
return nil, fmt.Errorf("unknown signature type: %d", signatureType)
}
func decodePGPSig(version uint8, r io.Reader) (*pkg.RpmSignature, error) {
var pubKeyAlgo, hashAlgo, pkgDate string
var keyID [8]byte
switch {
case version > 0x15:
sig := pgp4Sig{}
err := binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP v4 signature on decode: %w", err)
}
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
hashAlgo = hashLookup[sig.HashAlgo]
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
keyID = sig.KeyID
default:
sig := pgpSig{}
err := binary.Read(r, binary.BigEndian, &sig)
if err != nil {
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
}
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
hashAlgo = hashLookup[sig.HashAlgo]
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
keyID = sig.KeyID
}
return &pkg.RpmSignature{
PublicKeyAlgorithm: pubKeyAlgo,
HashAlgorithm: hashAlgo,
Created: pkgDate,
IssuerKeyID: fmt.Sprintf("%x", keyID),
}, nil
}
func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string {
digestAlgorithm, err := header.GetString(rpmutils.FILEDIGESTALGO)
logRpmArchiveErr(location, "file digest algo", err)
@@ -109,6 +269,6 @@ func parseEpoch(epoch string) *int {
func logRpmArchiveErr(location file.Location, operation string, err error) {
if err != nil {
log.Debugf("ERROR in parse_rpm_archive %s file: %s: %v", operation, location.RealPath, err)
log.WithFields("error", err, "operation", operation, "path", location.RealPath).Trace("unable to parse RPM archive")
}
}
@@ -2,8 +2,14 @@ package redhat
import (
"context"
"encoding/hex"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@@ -14,11 +20,15 @@ func TestParseRpmFiles(t *testing.T) {
abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")
zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm")
tests := []struct {
fixture string
expected []pkg.Package
name string
fixtureDir string
fixtureImage string
skipFiles bool
expected []pkg.Package
}{
{
fixture: "test-fixtures/rpms",
name: "go case",
fixtureDir: "test-fixtures/rpms",
expected: []pkg.Package{
{
Name: "abc",
@@ -37,8 +47,16 @@ func TestParseRpmFiles(t *testing.T) {
Release: "9.hg20160905.el7",
Version: "1.01",
SourceRpm: "abc-1.01-9.hg20160905.el7.src.rpm",
Size: 17396,
Vendor: "Fedora Project",
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Wed Sep 21 07:09:44 2016",
IssuerKeyID: "6a2faea2352c64e5",
},
},
Size: 17396,
Vendor: "Fedora Project",
Files: []pkg.RpmFileRecord{
{"/usr/bin/abc", 33261, 7120, file.Digest{"sha256", "8f8495a65c66762b60afa0c3949d81b275ca6fa0601696caba5af762f455d0b9"}, "root", "root", ""},
{"/usr/share/doc/abc-1.01", 16877, 4096, file.Digest{}, "root", "root", ""},
@@ -66,7 +84,15 @@ func TestParseRpmFiles(t *testing.T) {
Version: "1.0.3",
SourceRpm: "zork-1.0.3-1.el7.src.rpm",
Size: 262367,
Vendor: "Fedora Project",
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Mar 2 17:32:21 2021",
IssuerKeyID: "6a2faea2352c64e5",
},
},
Vendor: "Fedora Project",
Files: []pkg.RpmFileRecord{
{"/usr/bin/zork", 33261, 115440, file.Digest{"sha256", "31b2ffc20b676a8fff795a45308f584273b9c47e8f7e196b4f36220b2734b472"}, "root", "root", ""},
{"/usr/share/doc/zork-1.0.3", 16877, 38, file.Digest{}, "root", "root", ""},
@@ -83,20 +109,128 @@ func TestParseRpmFiles(t *testing.T) {
},
},
{
fixture: "test-fixtures/bad",
name: "bad rpms",
fixtureDir: "test-fixtures/bad",
},
{
name: "rpms with signatures from RSA header",
fixtureImage: "image-rpm-archive",
skipFiles: true,
expected: []pkg.Package{
{
Name: "postgresql14-server",
Version: "0:14.10-1PGDG.rhel9",
PURL: "pkg:rpm/postgresql14-server@14.10-1PGDG.rhel9?arch=x86_64&epoch=0&upstream=postgresql14-14.10-1PGDG.rhel9.src.rpm",
Locations: file.NewLocationSet(file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm")),
FoundBy: "rpm-archive-cataloger",
Type: pkg.RpmPkg,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("PostgreSQL", file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm"))),
Language: "",
CPEs: nil,
Metadata: pkg.RpmArchive{
Name: "postgresql14-server",
Version: "14.10",
Epoch: ref(0),
Arch: "x86_64",
Release: "1PGDG.rhel9",
SourceRpm: "postgresql14-14.10-1PGDG.rhel9.src.rpm",
Size: 24521699,
Signatures: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Jan 2 16:45:56 2024",
IssuerKeyID: "40bca2b408b40d20",
},
},
Vendor: "PostgreSQL Global Development Group",
// note: files are not asserted in this test
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var opts []cmp.Option
if test.skipFiles {
opts = append(opts, cmpopts.IgnoreFields(pkg.RpmArchive{}, "Files"))
}
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
WithCompareOptions(opts...).
FromDirectory(t, test.fixtureDir).
WithImageResolver(t, test.fixtureImage).
IgnoreLocationLayer().
Expects(test.expected, nil).
TestCataloger(t, NewArchiveCataloger())
})
}
}
func Test_parseRSA(t *testing.T) {
tests := []struct {
name string
data string
want *pkg.RpmSignature
wantErr assert.ErrorAssertionFunc
}{
{
name: "older RSA header",
data: "89021503050058d3e39b0946fca2c105b9de0102b12a1000a2b3d347b51142e83b2de5e03ba9096f6330b72c140e46200d662b01c78534d14fab2ad4f07325119386830dd590219f27a22e420680283c500c40e6fba95404884b0a0abca8f198030ddc03653b7db2883b8230687e9e73d43eb5a24dbabfa48bbb3d1151ed264744e5e8ca169b0c4673a1440a9b99e53e693c9722f6423833cd7795e3044227fb922e21b7c007f03e923fae3f04d1ac2e8581e68c6790115b6dccfc02c8cb41681ed84785df086d6e26008c257d088a524ba2e7a7a5f41ad26b106c67b87fe48118b69662db612c23d2140059286f1ba7764627def6867ad0e11fe3a01fb1422dabe6f5cdf4cd876dc4fadfd2364bc3ba3758db94aaf3b82368cba65cf762287f713eb7ddc773acf93b083c739577a7eaf1f99e7dcbb8db1da050490e9fb67c838448db060a9e619d318c96f03e4363808d84ce29e8c102c290cc2bfab5746f3d9ddc9eb8b428f3ad2678abb2d46e846ddca7fc41322d76a97be6d416b4750f23320ec725e082be4496483b4cd3a3d2c515b3c8a6e27541139d809245140303877b84842ed2dd0454a78b2dfb7d6213784697077a8167942ebda5995a28d8256957e33e301706c35944ae05c7a54a4dd89be654d26cefa5cf0f616bbeaf317138371b09c5bbd5531f716020e553354ce5dbce3d9bb72f21e1857408dfd5a35250ff40f61ae1e25409ae9d21db76b8878341f4762a22be2189",
// RSA/SHA1, Thu Mar 23 15:02:51 2017, Key ID 0946fca2c105b9de
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA1",
Created: "Thu Mar 23 15:02:51 2017",
IssuerKeyID: "0946fca2c105b9de",
},
},
{
name: "newer RSA header",
data: "89024a04000108003416210421cb256ae16fc54c6e652949702d426d350d275d050262804369161c72656c656e6740726f636b796c696e75782e6f7267000a0910702d426d350d275dc8910ffd14f0f80297481fea648e7ba5a74bce10c5faccc2bbe588caece04be34d304a6a445538afc97a7033d43c983d27cc8f5ee515b2dd92f3e03354c413e55372a4d19386eb0f2354f9a26ee5fc2e56dfda49555e4a58b49279b70cd2036b04f28125f85942f640f2984e29e079f26bf6f76831d83d95983aa084a3e7b6327be2e23d0d799c4b4d1cfb36147ddfb782bf9df7b331d97f4f46b38f968b6130d87b0ef6bb0d424390fe34e38092babed37440569a93f55f50a2bdb58be0259f35badf7e728bd49824ed47f69fa53b6e26736bde4d8358d959b090e88054c3e179745dc7377e41b54b4e10223f4859e88162c7c5ec64b78d36cf8a914c1c2deb8c4f19a70d406e70756a89195d6aee488a9b40b9dbb76b2c38e528eb88d08ec35774a48ed9ce4e0dfac45cb7613ad5921f54c61d3aae5d7b3ab0e2e6ff867ac8f395b37af78b5c01022a4a4e62f7a99425fccb7439880cd6b393a3050b2e9512693bc36f6fe9de2921dda59710a1508965065244cf9f0f8cfc5bd554777f1a84d2249339234d62f2441249f617ad7df4fb01367a91d3a880e86fdb84bc6d03a127b44a28c6ceadef89e438db9640aa59b8a3f460b07272511f8187a5f3b163c8fd1caa61667401bce2ccdb1c176c46be10ef8033903132cca5889fa3661b2fba590c41fa1c104c08426677bdbf745a52ccd28f581960cf9d7e4ede3b9584aacb2f20ef93",
// RSA/SHA256, Sun May 15 00:03:53 2022, Key ID 702d426d350d275d
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Sun May 15 00:03:53 2022",
IssuerKeyID: "702d426d350d275d",
},
},
{
name: "example from rocky9",
data: "8901b304000108001d162104d4bf08ae67a0b4c7a1dbccd240bca2b408b40d20050265943dc4000a091040bca2b408b40d203b270bff71678ffeb190833a19a82112f59eee64cba186ab454d4526e0b3c8797e723f6916daff1b1f18cbf53c0da5d398a3a42065e79e5ca939f721652f38400dd4cac1107a902b1dae880649437ad0242444f3f07115172cae0a207b7cf8340af2f4a94976325f1dc165d5c2a564be322c4e130adb6217e7138b689f08898c407b223aa1ff8f8d592f31eba2256c02fae70ce4022d688a487972646b8bf1b518b5d6549c1e60fd812134422d9fdb41cf799f5eab80e48b4ab7cff84362dc867ed1af1416dd78e92bcc59217de7064b9a015d94a5097788689b9b6fbdeea679cfe4a6947f73dc3a6c810f2cb999d279b01564422d1500fc1bd8bd1eefa2d60660127ffef24067354660f93c0faf81f4edd599dd7e4b77fe4bff6c7a0ea83530c817c38d1f2364175883c6ef7b6dec86ad282bdd5138b8597567db96810c4ed6454a4ab1d98f0425dcd8892a5d46ed9289cb3ae3e1f1e2663d3e8188e873428f6cf7163563ed3860edc4fee81522389508847e692e2d13310eb4b40f7fdd7eb364a0b2dc",
// RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
want: &pkg.RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Tue Jan 2 16:45:56 2024",
IssuerKeyID: "40bca2b408b40d20",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
data, err := hex.DecodeString(tt.data)
require.NoError(t, err)
got, err := parsePGP(data)
if !tt.wantErr(t, err) {
return
}
assert.Equal(t, tt.want, got)
})
}
}
func ref[T any](v T) *T {
return &v
}
func Test_corruptRpmArchive(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/bad/bad.rpm").
+76 -1
View File
@@ -2,11 +2,13 @@ package redhat
import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
rpmdb "github.com/anchore/go-rpmdb/pkg"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
@@ -69,6 +71,14 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
files, err := extractRpmFileRecords(resolver, *entry)
errs = unknown.Join(errs, err)
// there is a period of time when RPM DB entries contain both PGP and RSA signatures that are the same.
// This appears to be a holdover, where nowadays only the RSA Header is used.
sigs, err := parseSignatures(strings.TrimSpace(entry.PGP), strings.TrimSpace(entry.RSAHeader))
if err != nil {
log.WithFields("error", err, "location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)).Trace("unable to parse signatures for package %s", entry.Name)
sigs = nil
}
metadata := pkg.RpmDBEntry{
Name: entry.Name,
Version: entry.Version,
@@ -76,6 +86,7 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
Arch: entry.Arch,
Release: entry.Release,
SourceRpm: entry.SourceRpm,
Signatures: sigs,
Vendor: entry.Vendor,
Size: entry.Size,
ModularityLabel: &entry.Modularitylabel,
@@ -110,6 +121,70 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
return allPkgs, nil, errs
}
func parseSignatures(sigs ...string) ([]pkg.RpmSignature, error) {
var parsedSigs []pkg.RpmSignature
var errs error
for _, sig := range sigs {
if sig == "" {
continue
}
parts := strings.Split(sig, ",")
if len(parts) != 3 {
errs = errors.Join(fmt.Errorf("invalid signature format: %s", sig))
continue
}
methodParts := strings.SplitN(strings.TrimSpace(parts[0]), "/", 2)
if len(methodParts) != 2 {
errs = errors.Join(fmt.Errorf("invalid signature method format: %s", parts[0]))
continue
}
pka := strings.TrimSpace(methodParts[0])
hash := strings.TrimSpace(methodParts[1])
if pka == "" || hash == "" {
errs = errors.Join(fmt.Errorf("invalid signature method values: public-key=%q hash=%q", pka, hash))
continue
}
created := strings.TrimSpace(parts[1])
if created == "" {
errs = errors.Join(fmt.Errorf("invalid signature created value: %q", parts[1]))
continue
}
issuerFields := strings.Split(strings.TrimSpace(parts[2]), " ")
var issuer string
switch len(issuerFields) {
case 0:
errs = errors.Join(fmt.Errorf("no signature issuer value: %q", parts[2]))
case 1:
issuer = issuerFields[0]
default:
issuer = issuerFields[len(issuerFields)-1]
if issuer == "" {
errs = errors.Join(fmt.Errorf("invalid signature issuer value: %q", parts[2]))
continue
}
}
if len(issuer) < 5 {
errs = errors.Join(fmt.Errorf("invalid signature issuer length: %q", parts[2]))
continue
}
parsedSig := pkg.RpmSignature{
PublicKeyAlgorithm: pka,
HashAlgorithm: hash,
Created: created,
IssuerKeyID: issuer,
}
parsedSigs = append(parsedSigs, parsedSig)
}
return parsedSigs, errs
}
// The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version].
// RPM version comparison depends on comparing at least the version and release fields together as a subset of the
// naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@@ -222,6 +223,132 @@ func Test_corruptRpmDbEntry(t *testing.T) {
TestParser(t, parseRpmDB)
}
func TestParseSignatures(t *testing.T) {
tests := []struct {
name string
sigs []string
expected []pkg.RpmSignature
expectedError require.ErrorAssertionFunc
}{
{
name: "valid signature",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
},
{
name: "multiple valid signatures",
sigs: []string{
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
"DSA/SHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
{
PublicKeyAlgorithm: "DSA",
HashAlgorithm: "SHA1",
Created: "Tue Jun 14 09:45:12 2023",
IssuerKeyID: "123abc456def789",
},
},
},
{
name: "no signatures",
sigs: []string{},
expected: nil,
},
{
name: "empty signatures",
sigs: []string{"", "", ""},
expected: nil,
},
{
name: "invalid parts count",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022"},
expected: nil,
expectedError: require.Error,
},
{
name: "invalid method format",
sigs: []string{"RSASHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty method values",
sigs: []string{"/, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty created value",
sigs: []string{"RSA/SHA256, , Key ID 702d426d350d275d"},
expected: nil,
expectedError: require.Error,
},
{
name: "empty issuer value",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID "},
expected: nil,
expectedError: require.Error,
},
{
name: "issuer without prefix",
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, 702d426d350d275d"},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
},
{
name: "mixed valid and invalid signatures",
sigs: []string{
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
"DSASHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
},
expected: []pkg.RpmSignature{
{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
},
expectedError: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.expectedError == nil {
tt.expectedError = require.NoError
}
got, err := parseSignatures(tt.sigs...)
tt.expectedError(t, err)
if err != nil {
return
}
require.Equal(t, tt.expected, got)
})
}
}
func intRef(i int) *int {
return &i
}
@@ -0,0 +1,26 @@
FROM --platform=linux/amd64 rockylinux:9 AS rpm-downloader
# download a signed RPM (PostgreSQL in this example, known to be signed)
# using PostgreSQL official RPM which is signed with their GPG key
# $ rpm -Kv postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
# postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm:
# Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
# Header SHA256 digest: OK
# Header SHA1 digest: OK
# Payload SHA256 digest: OK
# MD5 digest: OK
#
# $ rpm -ivh --nodeps --force postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
# warning: postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm: Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
# Verifying... ################################# [100%]
# Preparing... ################################# [100%]
#
# $ rpm -q --qf '%{NAME}-%{VERSION}-%{RELEASE} %{RSAHEADER:pgpsig}\n' postgresql14-server-14.10-1PGDG.rhel9.x86_64
# postgresql14-server-14.10-1PGDG.rhel9 RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
RUN curl -O https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-9-x86_64/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
FROM scratch
COPY --from=rpm-downloader /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
+1 -1
View File
@@ -365,7 +365,7 @@ func (b *licenseBuilder) licensesFromEvidenceAndContent(evidence []licenses.Evid
func (b *licenseBuilder) licenseFromContentHash(content string) License {
hash := sha256HexFromString(content)
value := "LicenseRef-sha256:" + hash
value := "sha256:" + hash
return License{
Value: value,
+1 -1
View File
@@ -149,7 +149,7 @@ func TestLicenseSet_Add(t *testing.T) {
Locations: file.NewLocationSet(),
},
{
Value: "LicenseRef-sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Value: "sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Contents: readFileAsString("../../internal/licenses/test-fixtures/nvidia-software-and-cuda-supplement"),
Type: license.Declared,
},
+1 -1
View File
@@ -255,7 +255,7 @@ func TestFullText(t *testing.T) {
name: "Full Text field is populated with the correct full text and contents are given a sha256 as value",
value: fullText,
want: License{
Value: "LicenseRef-sha256:108067fa71229a2b98b9696af0ce21cd11d9639634c8bc94bda70ebedf291e5a",
Value: "sha256:108067fa71229a2b98b9696af0ce21cd11d9639634c8bc94bda70ebedf291e5a",
Type: license.Declared,
Contents: fullText,
},
+18
View File
@@ -2,6 +2,7 @@ package pkg
import (
"sort"
"strings"
"github.com/scylladb/go-set/strset"
@@ -32,6 +33,7 @@ type RpmDBEntry struct {
Arch string `json:"architecture"`
Release string `json:"release" cyclonedx:"release"`
SourceRpm string `json:"sourceRpm" cyclonedx:"sourceRpm"`
Signatures []RpmSignature `json:"signatures,omitempty" cyclonedx:"signatures"`
Size int `json:"size" cyclonedx:"size"`
Vendor string `json:"vendor"`
ModularityLabel *string `json:"modularityLabel,omitempty"`
@@ -40,6 +42,22 @@ type RpmDBEntry struct {
Files []RpmFileRecord `json:"files"`
}
type RpmSignature struct {
PublicKeyAlgorithm string `json:"algo"`
HashAlgorithm string `json:"hash"`
Created string `json:"created"`
IssuerKeyID string `json:"issuer"`
}
func (s RpmSignature) String() string {
if s.PublicKeyAlgorithm == "" && s.HashAlgorithm == "" && s.Created == "" && s.IssuerKeyID == "" {
return ""
}
// mimics the output you would see from rpm -q --qf "%{RSAHEADER}"
// e.g."RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"
return strings.Join([]string{s.PublicKeyAlgorithm + "/" + s.HashAlgorithm, s.Created, "Key ID " + s.IssuerKeyID}, ", ")
}
// RpmFileRecord represents the file metadata for a single file attributed to a RPM package.
type RpmFileRecord struct {
Path string `json:"path"`
+47
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/go-test/deep"
"github.com/stretchr/testify/assert"
)
func TestRpmMetadata_FileOwner(t *testing.T) {
@@ -46,3 +47,49 @@ func TestRpmMetadata_FileOwner(t *testing.T) {
})
}
}
func TestRpmSignature_String(t *testing.T) {
tests := []struct {
name string
signature RpmSignature
expected string
}{
{
name: "standard signature",
signature: RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "SHA256",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "702d426d350d275d",
},
expected: "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
},
{
name: "empty fields",
signature: RpmSignature{
PublicKeyAlgorithm: "",
HashAlgorithm: "",
Created: "",
IssuerKeyID: "",
},
expected: "",
},
{
name: "partial empty fields",
signature: RpmSignature{
PublicKeyAlgorithm: "RSA",
HashAlgorithm: "",
Created: "Mon May 16 12:32:55 2022",
IssuerKeyID: "",
},
expected: "RSA/, Mon May 16 12:32:55 2022, Key ID ",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.signature.String()
assert.Equal(t, tt.expected, result)
})
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ package cli
import "testing"
func Test_Licenses(t *testing.T) {
testImage := getFixtureImage(t, "image-unknown-licenses")
testImage := getFixtureImage(t, "image-pkg-coverage")
tests := []struct {
name string
args []string
@@ -1,3 +0,0 @@
FROM alpine@sha256:c5c5fda71656f28e49ac9c5416b3643eaa6a108a8093151d6d1afc9463be8e33
RUN rm -rf /lib/apk/db/installed
COPY . /home/files
+1 -1
View File
@@ -169,7 +169,7 @@ func assertUnknownLicenseContent(required bool) traitAssertion {
for _, pkg := range data.Artifacts {
for _, lic := range pkg.Licenses {
if strings.Contains(lic.SPDXExpression, "UNKNOWN") && required {
if strings.Contains(lic.Value, "sha256") && required {
assert.NotZero(tb, len(lic.Contents))
} else {
assert.Empty(tb, lic.Contents)