mirror of
https://github.com/anchore/syft.git
synced 2026-10-24 03:52:21 +02:00
Compare commits
+2
-2
@@ -42,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.4.0
|
||||
want: v2.4.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -111,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.57.0
|
||||
want: v2.58.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the changes along with any relevant motivation and context,
|
||||
or link to an issue where this is explained.
|
||||
|
||||
<!-- If this completes an issue, please include: -->
|
||||
|
||||
- Fixes #
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Delete any that are not relevant -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (please discuss with the team first; Syft is 1.0 software and we won't accept breaking changes without going to 2.0)
|
||||
- [ ] Documentation (updates the documentation)
|
||||
- [ ] Chore (improve the developer experience, fix a test flake, etc, without changing the visible behavior of Syft)
|
||||
- [ ] Performance (make Syft run faster or use less memory, without changing visible behavior much)
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] I have added unit tests that cover changed behavior
|
||||
- [ ] I have tested my code in common scenarios and confirmed there are no regressions
|
||||
- [ ] I have added comments to my code, particularly in hard-to-understand sections
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache@2cdf405574d6ef1f33a1d12acccd3ae82f47b3f2 #v4.1.0
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 #v3.26.8
|
||||
uses: github/codeql-action/init@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea #v3.26.11
|
||||
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@294a9d92911152fe08befb9ec03e240add280cb3 #v3.26.8
|
||||
uses: github/codeql-action/autobuild@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea #v3.26.11
|
||||
|
||||
# ℹ️ 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@294a9d92911152fe08befb9ec03e240add280cb3 #v3.26.8
|
||||
uses: github/codeql-action/analyze@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea #v3.26.11
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Update version file
|
||||
run: make ci-release-version-file
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Check if running on main
|
||||
if: github.ref != 'refs/heads/main'
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
name: "Manage Awaiting Response Label"
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
|
||||
secrets:
|
||||
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/save@2cdf405574d6ef1f33a1d12acccd3ae82f47b3f2 #v4.1.0
|
||||
with:
|
||||
# we need to preserve the snapshot data itself as well as the task data that confirms if the
|
||||
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@2cdf405574d6ef1f33a1d12acccd3ae82f47b3f2 #v4.1.0
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -139,9 +139,9 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.6.0
|
||||
uses: sigstore/cosign-installer@v3.7.0
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@2cdf405574d6ef1f33a1d12acccd3ae82f47b3f2 #v4.1.0
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@2cdf405574d6ef1f33a1d12acccd3ae82f47b3f2 #v4.1.0
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
- Acceptance-Mac
|
||||
- Cli-Linux
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0
|
||||
|
||||
- name: Delete snapshot cache
|
||||
run: gh cache delete "snapshot-build-${{ github.run_id }}"
|
||||
|
||||
@@ -53,6 +53,9 @@ type Catalog struct {
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
|
||||
// configuration for inclusion of unknown information within elements
|
||||
Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
@@ -71,6 +74,7 @@ func DefaultCatalog() Catalog {
|
||||
Java: defaultJavaConfig(),
|
||||
File: defaultFileConfig(),
|
||||
Relationships: defaultRelationshipsConfig(),
|
||||
Unknowns: defaultUnknowns(),
|
||||
Source: defaultSourceConfig(),
|
||||
Parallelism: 1,
|
||||
}
|
||||
@@ -82,6 +86,7 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
|
||||
WithParallelism(cfg.Parallelism).
|
||||
WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
|
||||
WithComplianceConfig(cfg.ToComplianceConfig()).
|
||||
WithUnknownsConfig(cfg.ToUnknownsConfig()).
|
||||
WithSearchConfig(cfg.ToSearchConfig()).
|
||||
WithPackagesConfig(cfg.ToPackagesConfig()).
|
||||
WithFilesConfig(cfg.ToFilesConfig()).
|
||||
@@ -114,6 +119,13 @@ func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
|
||||
return cataloging.UnknownsConfig{
|
||||
IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
|
||||
IncludeUnexpandedArchives: cfg.Unknowns.UnexpandedArchives,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
type unknownsConfig struct {
|
||||
RemoveWhenPackagesDefined bool `json:"remove-when-packages-defined" yaml:"remove-when-packages-defined" mapstructure:"remove-when-packages-defined"`
|
||||
ExecutablesWithoutPackages bool `json:"executables-without-packages" yaml:"executables-without-packages" mapstructure:"executables-without-packages"`
|
||||
UnexpandedArchives bool `json:"unexpanded-archives" yaml:"unexpanded-archives" mapstructure:"unexpanded-archives"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*unknownsConfig)(nil)
|
||||
|
||||
func (o *unknownsConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.RemoveWhenPackagesDefined, `remove unknown errors on files with discovered packages`)
|
||||
descriptions.Add(&o.ExecutablesWithoutPackages, `include executables without any identified packages`)
|
||||
descriptions.Add(&o.UnexpandedArchives, `include archives which were not expanded and searched`)
|
||||
}
|
||||
|
||||
func defaultUnknowns() unknownsConfig {
|
||||
def := cataloging.DefaultUnknownsConfig()
|
||||
return unknownsConfig{
|
||||
RemoveWhenPackagesDefined: def.RemoveWhenPackagesDefined,
|
||||
ExecutablesWithoutPackages: def.IncludeExecutablesWithoutPackages,
|
||||
UnexpandedArchives: def.IncludeUnexpandedArchives,
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f
|
||||
github.com/anchore/stereoscope v0.0.3
|
||||
github.com/anchore/stereoscope v0.0.4-0.20241005180410-efa76446cc1c
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/docker v27.3.0+incompatible
|
||||
github.com/docker/docker v27.3.1+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/knqyf263/go-rpmdb v0.1.1
|
||||
@@ -79,7 +79,7 @@ require (
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/net v0.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.33.1
|
||||
)
|
||||
@@ -227,11 +227,11 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
|
||||
@@ -115,8 +115,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f h1:B/E9ixKNCasntpoch61NDaQyGPDXLEJlL+B9B/PbdbA=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.3 h1:JRPHySy8S6P+Ff3IDiQ29ap1i8/laUQxDk9K1eFh/2U=
|
||||
github.com/anchore/stereoscope v0.0.3/go.mod h1:5DJheGPjVRsSqegTB24Zi6SCHnYQnA519yeIG+RG+I4=
|
||||
github.com/anchore/stereoscope v0.0.4-0.20241005180410-efa76446cc1c h1:JXezMk8fF5ns4AgRGW49SGfoRgDjJHsDmcpNw272jkU=
|
||||
github.com/anchore/stereoscope v0.0.4-0.20241005180410-efa76446cc1c/go.mod h1:GMupz2FoBhy5RTTmawU06c2pZxgVTceahLWiwJef2uI=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -229,8 +229,8 @@ github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2
|
||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.3.0+incompatible h1:BNb1QY6o4JdKpqwi9IB+HUYcRRrVN4aGFUTvDmWYK1A=
|
||||
github.com/docker/docker v27.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -477,8 +477,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
|
||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0 h1:wmZVuAcEkZRT+Aq1xXpE8IGat4vE5WXOMmBpbQqERXw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -864,8 +864,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -954,8 +954,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1067,15 +1067,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1088,8 +1088,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -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.17"
|
||||
JSONSchemaVersion = "16.0.18"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -19,12 +18,13 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
|
||||
zipReader, err := OpenZip(archivePath)
|
||||
manifest := make(ZipFileManifest)
|
||||
if err != nil {
|
||||
return manifest, fmt.Errorf("unable to open zip archive (%s): %w", archivePath, err)
|
||||
log.Debugf("unable to open zip archive (%s): %v", archivePath, err)
|
||||
return manifest, err
|
||||
}
|
||||
defer func() {
|
||||
err = zipReader.Close()
|
||||
if err != nil {
|
||||
log.Warnf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
log.Debugf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically:
|
||||
@@ -46,7 +48,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
||||
// need to find the start of the archive and keep track of this offset.
|
||||
offset, err := findArchiveStartOffset(f, fi.Size())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find beginning of zip archive=%q : %w", filepath, err)
|
||||
log.Debugf("cannot find beginning of zip archive=%q : %v", filepath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
@@ -62,7 +65,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
||||
|
||||
r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open ZipReadCloser @ %q: %w", filepath, err)
|
||||
log.Debugf("unable to open ZipReadCloser @ %q: %v", filepath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ZipReadCloser{
|
||||
|
||||
@@ -22,7 +22,13 @@ func Parse(reader io.Reader, l file.Location) (licenses []pkg.License, err error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cov := licensecheck.Scan(contents)
|
||||
|
||||
scanner, err := licensecheck.NewScanner(licensecheck.BuiltinLicenses())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cov := scanner.Scan(contents)
|
||||
if cov.Percent < coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return licenses, nil
|
||||
|
||||
@@ -9,15 +9,18 @@ func EvidentBy(catalog *pkg.Collection) []artifact.Relationship {
|
||||
var edges []artifact.Relationship
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists || v != pkg.PrimaryEvidenceAnnotation {
|
||||
// skip non-primary evidence from being expressed as a relationship.
|
||||
// note: this may be configurable in the future.
|
||||
continue
|
||||
kind := pkg.SupportingEvidenceAnnotation
|
||||
if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; exists {
|
||||
kind = v
|
||||
}
|
||||
|
||||
edges = append(edges, artifact.Relationship{
|
||||
From: p,
|
||||
To: l.Coordinates,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
Data: map[string]string{
|
||||
"kind": kind,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
type Executor struct {
|
||||
@@ -35,6 +37,12 @@ func NewTaskExecutor(tasks []Task, numWorkers int) *Executor {
|
||||
}
|
||||
|
||||
func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
|
||||
var lock sync.Mutex
|
||||
withLock := func(fn func()) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
fn()
|
||||
}
|
||||
var errs error
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < p.numWorkers; i++ {
|
||||
@@ -48,9 +56,16 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
||||
return
|
||||
}
|
||||
|
||||
if err := runTaskSafely(ctx, tsk, resolver, s); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||
prog.SetError(err)
|
||||
err := runTaskSafely(ctx, tsk, resolver, s)
|
||||
unknowns, err := unknown.ExtractCoordinateErrors(err)
|
||||
if len(unknowns) > 0 {
|
||||
appendUnknowns(s, tsk.Name(), unknowns)
|
||||
}
|
||||
if err != nil {
|
||||
withLock(func() {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||
prog.SetError(err)
|
||||
})
|
||||
}
|
||||
prog.Increment()
|
||||
}
|
||||
@@ -62,6 +77,19 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
||||
return errs
|
||||
}
|
||||
|
||||
func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknown.CoordinateError) {
|
||||
if accessor, ok := builder.(sbomsync.Accessor); ok {
|
||||
accessor.WriteToSBOM(func(sb *sbom.SBOM) {
|
||||
for _, u := range unknowns {
|
||||
if sb.Artifacts.Unknowns == nil {
|
||||
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||
}
|
||||
sb.Artifacts.Unknowns[u.Coordinates] = append(sb.Artifacts.Unknowns[u.Coordinates], formatUnknown(u.Reason.Error(), taskName))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
|
||||
// handle individual cataloger panics
|
||||
defer func() {
|
||||
|
||||
+16
-21
@@ -3,7 +3,6 @@ package task
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -32,15 +31,12 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
|
||||
}
|
||||
|
||||
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to catalog file digests: %w", err)
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileDigests = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-digest-cataloger", fn)
|
||||
@@ -62,15 +58,12 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
||||
}
|
||||
|
||||
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileMetadata = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-metadata-cataloger", fn)
|
||||
@@ -87,15 +80,12 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(ctx, resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileContents = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-content-cataloger", fn)
|
||||
@@ -112,15 +102,12 @@ func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config)
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.Executables = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn)
|
||||
@@ -134,9 +121,10 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
|
||||
}
|
||||
|
||||
if selection == file.FilesOwnedByPackageSelection {
|
||||
var coordinates []file.Coordinates
|
||||
var coordinates file.CoordinateSet
|
||||
|
||||
accessor.ReadFromSBOM(func(sbom *sbom.SBOM) {
|
||||
// get any file coordinates that are owned by a package
|
||||
for _, r := range sbom.Relationships {
|
||||
if r.Type != artifact.ContainsRelationship {
|
||||
continue
|
||||
@@ -145,16 +133,23 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
|
||||
continue
|
||||
}
|
||||
if c, ok := r.To.(file.Coordinates); ok {
|
||||
coordinates = append(coordinates, c)
|
||||
coordinates.Add(c)
|
||||
}
|
||||
}
|
||||
|
||||
// get any file coordinates referenced by a package directly
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
coordinates.Add(p.Locations.CoordinateSet().ToSlice()...)
|
||||
}
|
||||
})
|
||||
|
||||
if len(coordinates) == 0 {
|
||||
coords := coordinates.ToSlice()
|
||||
|
||||
if len(coords) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return coordinates, true
|
||||
return coords, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
||||
@@ -103,9 +103,6 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
t := bus.StartCatalogerTask(info, -1, "")
|
||||
|
||||
pkgs, relationships, err := c.Catalog(ctx, resolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to catalog packages with %q: %w", catalogerName, err)
|
||||
}
|
||||
|
||||
log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs))
|
||||
|
||||
@@ -120,7 +117,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
t.SetCompleted()
|
||||
log.WithFields("name", catalogerName).Trace("package cataloger completed")
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
tags = append(tags, pkgcataloging.PackageTag)
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const unknownsLabelerTaskName = "unknowns-labeler"
|
||||
|
||||
func NewUnknownsLabelerTask(cfg cataloging.UnknownsConfig) Task {
|
||||
return NewTask(unknownsLabelerTaskName, unknownsLabelerTask{cfg}.processUnknowns)
|
||||
}
|
||||
|
||||
type unknownsLabelerTask struct {
|
||||
cataloging.UnknownsConfig
|
||||
}
|
||||
|
||||
// processUnknowns removes unknown entries that have valid packages reported for the locations
|
||||
func (c unknownsLabelerTask) processUnknowns(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
accessor.WriteToSBOM(func(s *sbom.SBOM) {
|
||||
c.finalize(resolver, s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c unknownsLabelerTask) finalize(resolver file.Resolver, s *sbom.SBOM) {
|
||||
hasPackageReference := coordinateReferenceLookup(resolver, s)
|
||||
|
||||
if c.RemoveWhenPackagesDefined {
|
||||
for coords := range s.Artifacts.Unknowns {
|
||||
if !hasPackageReference(coords) {
|
||||
continue
|
||||
}
|
||||
delete(s.Artifacts.Unknowns, coords)
|
||||
}
|
||||
}
|
||||
|
||||
if s.Artifacts.Unknowns == nil {
|
||||
s.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||
}
|
||||
|
||||
if c.IncludeExecutablesWithoutPackages {
|
||||
for coords := range s.Artifacts.Executables {
|
||||
if !hasPackageReference(coords) {
|
||||
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], formatUnknown("no package identified in executable file", unknownsLabelerTaskName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.IncludeUnexpandedArchives {
|
||||
for coords := range s.Artifacts.FileMetadata {
|
||||
unarchiver, notArchiveErr := archiver.ByExtension(coords.RealPath)
|
||||
if unarchiver != nil && notArchiveErr == nil && !hasPackageReference(coords) {
|
||||
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], "archive not cataloged")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatUnknown(err string, task ...string) string {
|
||||
return strings.Join(task, "/") + ": " + err
|
||||
}
|
||||
|
||||
func coordinateReferenceLookup(resolver file.Resolver, s *sbom.SBOM) func(coords file.Coordinates) bool {
|
||||
allPackageCoords := file.NewCoordinateSet()
|
||||
|
||||
// include all directly included locations that result in packages
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
allPackageCoords.Add(p.Locations.CoordinateSet().ToSlice()...)
|
||||
}
|
||||
|
||||
// include owned files, for example specified by package managers.
|
||||
// relationships for these owned files may be disabled, but we always want to include them
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if f, ok := p.Metadata.(pkg.FileOwner); ok {
|
||||
for _, ownedFilePath := range f.OwnedFiles() {
|
||||
// resolve these owned files, as they may have symlinks
|
||||
// but coordinates we will test against are always absolute paths
|
||||
locations, err := resolver.FilesByPath(ownedFilePath)
|
||||
if err != nil {
|
||||
log.Debugf("unable to resolve owned file '%s': %v", ownedFilePath, err)
|
||||
}
|
||||
for _, loc := range locations {
|
||||
allPackageCoords.Add(loc.Coordinates)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include relationships
|
||||
for _, r := range s.Relationships {
|
||||
_, fromPkgOk := r.From.(pkg.Package)
|
||||
fromFile, fromFileOk := r.From.(file.Coordinates)
|
||||
_, toPkgOk := r.To.(pkg.Package)
|
||||
toFile, toFileOk := r.To.(file.Coordinates)
|
||||
if fromPkgOk && toFileOk {
|
||||
allPackageCoords.Add(toFile)
|
||||
} else if fromFileOk && toPkgOk {
|
||||
allPackageCoords.Add(fromFile)
|
||||
}
|
||||
}
|
||||
|
||||
return allPackageCoords.Contains
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
type hasCoordinates interface {
|
||||
GetCoordinates() file.Coordinates
|
||||
}
|
||||
|
||||
type CoordinateError struct {
|
||||
Coordinates file.Coordinates
|
||||
Reason error
|
||||
}
|
||||
|
||||
var _ error = (*CoordinateError)(nil)
|
||||
|
||||
func (u *CoordinateError) Error() string {
|
||||
if u.Coordinates.FileSystemID == "" {
|
||||
return fmt.Sprintf("%s: %v", u.Coordinates.RealPath, u.Reason)
|
||||
}
|
||||
return fmt.Sprintf("%s (%s): %v", u.Coordinates.RealPath, u.Coordinates.FileSystemID, u.Reason)
|
||||
}
|
||||
|
||||
// New returns a new CoordinateError unless the reason is a CoordinateError itself, in which case
|
||||
// reason will be returned directly or if reason is nil, nil will be returned
|
||||
func New(coords hasCoordinates, reason error) *CoordinateError {
|
||||
if reason == nil {
|
||||
return nil
|
||||
}
|
||||
coordinates := coords.GetCoordinates()
|
||||
reasonCoordinateError := &CoordinateError{}
|
||||
if errors.As(reason, &reasonCoordinateError) {
|
||||
// if the reason is already a coordinate error, it is potentially for a different location,
|
||||
// so we do not want to surface this location having an error
|
||||
return reasonCoordinateError
|
||||
}
|
||||
return &CoordinateError{
|
||||
Coordinates: coordinates,
|
||||
Reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
// Newf returns a new CoordinateError with a reason of an error created from given format and args
|
||||
func Newf(coords hasCoordinates, format string, args ...any) *CoordinateError {
|
||||
return New(coords, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// Append returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end
|
||||
func Append(errs error, coords hasCoordinates, reason error) error {
|
||||
return Join(errs, New(coords, reason))
|
||||
}
|
||||
|
||||
// Appendf returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end,
|
||||
// created from the given reason and args
|
||||
func Appendf(errs error, coords hasCoordinates, format string, args ...any) error {
|
||||
return Append(errs, coords, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// Join joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||
// from other sources, including errors.Join, multierror.Append, and unknown.Join
|
||||
func Join(errs ...error) error {
|
||||
var out []error
|
||||
for _, err := range errs {
|
||||
// append errors, de-duplicated
|
||||
for _, e := range flatten(err) {
|
||||
if containsErr(out, e) {
|
||||
continue
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
if len(out) == 1 {
|
||||
return out[0]
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.Join(out...)
|
||||
}
|
||||
|
||||
// Joinf joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||
// from other sources, including errors.Join, multierror.Append, and unknown.Join and appending a new error,
|
||||
// created from the format and args provided -- the error is NOT a CoordinateError
|
||||
func Joinf(errs error, format string, args ...any) error {
|
||||
return Join(errs, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// IfEmptyf returns a new Errorf-formatted error, only when the provided slice is empty or nil when
|
||||
// the slice has entries
|
||||
func IfEmptyf[T any](emptyTest []T, format string, args ...any) error {
|
||||
if len(emptyTest) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractCoordinateErrors extracts all coordinate errors returned, and any _additional_ errors in the graph
|
||||
// are encapsulated in the second, error return parameter
|
||||
func ExtractCoordinateErrors(err error) (coordinateErrors []CoordinateError, remainingErrors error) {
|
||||
remainingErrors = visitErrors(err, func(e error) error {
|
||||
if coordinateError, _ := e.(*CoordinateError); coordinateError != nil {
|
||||
coordinateErrors = append(coordinateErrors, *coordinateError)
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
})
|
||||
return coordinateErrors, remainingErrors
|
||||
}
|
||||
|
||||
func flatten(errs ...error) []error {
|
||||
var out []error
|
||||
for _, err := range errs {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
// turn all errors nested under a coordinate error to individual coordinate errors
|
||||
if e, ok := err.(*CoordinateError); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
for _, r := range flatten(e.Reason) {
|
||||
out = append(out, New(e.Coordinates, r))
|
||||
}
|
||||
} else
|
||||
// from multierror.Append
|
||||
if e, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, flatten(e.WrappedErrors()...)...)
|
||||
} else
|
||||
// from errors.Join
|
||||
if e, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, flatten(e.Unwrap()...)...)
|
||||
} else {
|
||||
out = append(out, err)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// containsErr returns true if a duplicate error is found
|
||||
func containsErr(out []error, err error) bool {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Tracef("error comparing errors: %v", err)
|
||||
}
|
||||
}()
|
||||
for _, e := range out {
|
||||
if e == err {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
|
||||
// the object graph
|
||||
func visitErrors(err error, fn func(error) error) error {
|
||||
// unwrap errors from errors.Join
|
||||
if errs, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
var out []error
|
||||
for _, e := range errs.Unwrap() {
|
||||
out = append(out, visitErrors(e, fn))
|
||||
}
|
||||
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||
return errors.Join(out...)
|
||||
}
|
||||
// unwrap errors from multierror.Append -- these also implement Unwrap() error, so check this first
|
||||
if errs, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||
var out []error
|
||||
for _, e := range errs.WrappedErrors() {
|
||||
out = append(out, visitErrors(e, fn))
|
||||
}
|
||||
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||
return errors.Join(out...)
|
||||
}
|
||||
// unwrap singly wrapped errors
|
||||
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||
wrapped := e.Unwrap()
|
||||
got := visitErrors(wrapped, fn)
|
||||
if got == nil {
|
||||
return nil
|
||||
}
|
||||
if wrapped.Error() != got.Error() {
|
||||
prefix := strings.TrimSuffix(err.Error(), wrapped.Error())
|
||||
return fmt.Errorf("%s%w", prefix, got)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return fn(err)
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func Test_visitErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in error
|
||||
transform func(error) error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "return",
|
||||
in: fmt.Errorf("err1"),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "err1",
|
||||
},
|
||||
{
|
||||
name: "omit",
|
||||
in: fmt.Errorf("err1"),
|
||||
transform: func(_ error) error {
|
||||
return nil
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "wrapped return",
|
||||
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "wrapped: err1",
|
||||
},
|
||||
{
|
||||
name: "wrapped omit",
|
||||
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "joined return",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "joined omit",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(_ error) error {
|
||||
return nil
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "joined omit first",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "err2",
|
||||
},
|
||||
{
|
||||
name: "joined wrapped return",
|
||||
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "wrapped: err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "joined wrapped omit first",
|
||||
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "err2",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
gotErr := visitErrors(test.in, test.transform)
|
||||
got := fmt.Sprintf("%v", gotErr)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Join(t *testing.T) {
|
||||
err1 := fmt.Errorf("err1")
|
||||
err2 := fmt.Errorf("err2")
|
||||
|
||||
tests := []struct {
|
||||
name string ``
|
||||
in []error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
in: []error{fmt.Errorf("err")},
|
||||
expected: "err",
|
||||
},
|
||||
{
|
||||
name: "wrapped",
|
||||
in: []error{fmt.Errorf("outer: %w", fmt.Errorf("err"))},
|
||||
expected: "outer: err",
|
||||
},
|
||||
{
|
||||
name: "wrapped joined",
|
||||
in: []error{errors.Join(fmt.Errorf("outer: %w", fmt.Errorf("err1")), fmt.Errorf("err2"))},
|
||||
expected: "outer: err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "duplicates",
|
||||
in: []error{err1, err1, err2},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "nested duplicates",
|
||||
in: []error{errors.Join(err1, err2), err1, err2},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "nested duplicates coords",
|
||||
in: []error{New(file.NewLocation("l1"), errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2"))), fmt.Errorf("err1"), fmt.Errorf("err2")},
|
||||
expected: "l1: err1\nl1: err2\nerr1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "all nil",
|
||||
in: []error{nil, nil, nil},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := Join(test.in...)
|
||||
if test.expected == "" {
|
||||
require.Nil(t, got)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, test.expected, got.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_flatten(t *testing.T) {
|
||||
coords := file.Coordinates{
|
||||
RealPath: "real/path",
|
||||
}
|
||||
e1 := fmt.Errorf("e1")
|
||||
e2 := fmt.Errorf("e2")
|
||||
c1 := New(coords, fmt.Errorf("c1"))
|
||||
c2 := New(coords, fmt.Errorf("c2"))
|
||||
tests := []struct {
|
||||
name string ``
|
||||
in error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
in: errors.Join(e1, e2),
|
||||
expected: "e1//e2",
|
||||
},
|
||||
{
|
||||
name: "coords",
|
||||
in: New(coords, e1),
|
||||
expected: "real/path: e1",
|
||||
},
|
||||
{
|
||||
name: "coords with joined children",
|
||||
in: New(coords, errors.Join(e1, e2)),
|
||||
expected: "real/path: e1//real/path: e2",
|
||||
},
|
||||
{
|
||||
name: "very nested",
|
||||
in: errors.Join(errors.Join(errors.Join(errors.Join(e1, c1), e2), c2), e2),
|
||||
expected: "e1//real/path: c1//e2//real/path: c2//e2",
|
||||
},
|
||||
}
|
||||
toString := func(errs ...error) string {
|
||||
var parts []string
|
||||
for _, e := range errs {
|
||||
parts = append(parts, e.Error())
|
||||
}
|
||||
return strings.Join(parts, "//")
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := flatten(test.in)
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, test.expected, toString(got...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Append(t *testing.T) {
|
||||
e1 := New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||
e2 := Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||
e3 := Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l1: e1\nl2: e2\nl3: e3", e3.Error())
|
||||
|
||||
e1 = New(file.NewLocation("l1"), nil)
|
||||
require.Nil(t, e1)
|
||||
e2 = Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l2: e2\nl3: e3", e3.Error())
|
||||
|
||||
e1 = New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||
e2 = Append(e1, file.NewLocation("l2"), nil)
|
||||
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l1: e1\nl3: e3", e3.Error())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.17/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.18/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -777,6 +777,12 @@
|
||||
},
|
||||
"executable": {
|
||||
"$ref": "#/$defs/Executable"
|
||||
},
|
||||
"unknowns": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cataloging
|
||||
|
||||
type UnknownsConfig struct {
|
||||
RemoveWhenPackagesDefined bool
|
||||
IncludeExecutablesWithoutPackages bool
|
||||
IncludeUnexpandedArchives bool
|
||||
}
|
||||
|
||||
func DefaultUnknownsConfig() UnknownsConfig {
|
||||
return UnknownsConfig{
|
||||
RemoveWhenPackagesDefined: true,
|
||||
IncludeExecutablesWithoutPackages: true,
|
||||
IncludeUnexpandedArchives: true,
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -95,7 +95,7 @@ func NewAttributes(cpeStr string) (Attributes, error) {
|
||||
}
|
||||
|
||||
// ensure that this Attributes can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
if err = ValidateString(c.String()); err != nil {
|
||||
return Attributes{}, err
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -17,6 +17,7 @@ func Test_NewAttributes(t *testing.T) {
|
||||
name string
|
||||
input string
|
||||
expected Attributes
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
@@ -33,14 +34,21 @@ func Test_NewAttributes(t *testing.T) {
|
||||
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
|
||||
expected: MustAttributes(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
},
|
||||
{
|
||||
name: "null byte in version for some reason",
|
||||
input: "cpe:2.3:a:oracle:openjdk:11.0.22+7\u0000-J-ms8m:*:*:*:*:*:*:*",
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := NewAttributes(test.input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error while creating Attributes: %+v", err)
|
||||
if test.wantErr != nil {
|
||||
test.wantErr(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if d := cmp.Diff(actual, test.expected); d != "" {
|
||||
t.Errorf("Attributes mismatch (-want +got):\n%s", d)
|
||||
|
||||
@@ -22,6 +22,7 @@ type CreateSBOMConfig struct {
|
||||
Compliance cataloging.ComplianceConfig
|
||||
Search cataloging.SearchConfig
|
||||
Relationships cataloging.RelationshipsConfig
|
||||
Unknowns cataloging.UnknownsConfig
|
||||
DataGeneration cataloging.DataGenerationConfig
|
||||
Packages pkgcataloging.Config
|
||||
Files filecataloging.Config
|
||||
@@ -113,6 +114,12 @@ func (c *CreateSBOMConfig) WithRelationshipsConfig(cfg cataloging.RelationshipsC
|
||||
return c
|
||||
}
|
||||
|
||||
// WithUnknownsConfig allows for defining the specific behavior dealing with unknowns
|
||||
func (c *CreateSBOMConfig) WithUnknownsConfig(cfg cataloging.UnknownsConfig) *CreateSBOMConfig {
|
||||
c.Unknowns = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying
|
||||
// target being scanned that should be generated after package creation.
|
||||
func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig {
|
||||
@@ -173,6 +180,7 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
// generate package and file tasks based on the configuration
|
||||
environmentTasks := c.environmentTasks()
|
||||
relationshipsTasks := c.relationshipTasks(src)
|
||||
unknownTasks := c.unknownsTasks()
|
||||
fileTasks := c.fileTasks()
|
||||
pkgTasks, selectionEvidence, err := c.packageTasks(src)
|
||||
if err != nil {
|
||||
@@ -192,6 +200,11 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
taskGroups = append(taskGroups, relationshipsTasks)
|
||||
}
|
||||
|
||||
// all unknowns tasks should happen after all scanning is complete
|
||||
if len(unknownTasks) > 0 {
|
||||
taskGroups = append(taskGroups, unknownTasks)
|
||||
}
|
||||
|
||||
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
|
||||
taskGroups = append(
|
||||
[][]task.Task{
|
||||
@@ -338,6 +351,18 @@ func (c *CreateSBOMConfig) environmentTasks() []task.Task {
|
||||
return tsks
|
||||
}
|
||||
|
||||
// unknownsTasks returns a set of tasks that perform any necessary post-processing
|
||||
// to identify SBOM elements as unknowns
|
||||
func (c *CreateSBOMConfig) unknownsTasks() []task.Task {
|
||||
var tasks []task.Task
|
||||
|
||||
if t := task.NewUnknownsLabelerTask(c.Unknowns); t != nil {
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
func (c *CreateSBOMConfig) validate() error {
|
||||
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
|
||||
if !c.Relationships.PackageFileOwnershipOverlap {
|
||||
|
||||
@@ -90,6 +90,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -108,6 +109,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -127,6 +129,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -145,6 +148,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -163,6 +167,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
// note: there are no file catalogers in their own group
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -184,6 +189,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
fileCatalogerNames(true, true, true)...,
|
||||
),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -204,6 +210,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -224,6 +231,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -244,6 +252,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -265,6 +274,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -285,6 +295,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -385,6 +396,10 @@ func relationshipCatalogerNames() []string {
|
||||
return []string{"relationships-cataloger"}
|
||||
}
|
||||
|
||||
func unknownsTaskNames() []string {
|
||||
return []string{"unknowns-labeler"}
|
||||
}
|
||||
|
||||
func environmentCatalogerNames() []string {
|
||||
return []string{"environment-cataloger"}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
@@ -45,6 +46,8 @@ func NewCataloger(cfg Config) *Cataloger {
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
||||
var errs error
|
||||
|
||||
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
||||
@@ -61,7 +64,10 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
||||
for _, loc := range locs {
|
||||
prog.AtomicStage.Set(loc.Path())
|
||||
|
||||
exec := processExecutableLocation(loc, resolver)
|
||||
exec, err := processExecutableLocation(loc, resolver)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, loc, err)
|
||||
}
|
||||
|
||||
if exec != nil {
|
||||
prog.Increment()
|
||||
@@ -74,30 +80,24 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) *file.Executable {
|
||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file.Executable, error) {
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||
return nil
|
||||
return nil, fmt.Errorf("unable to get file contents: %w", err)
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
uReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
||||
return nil
|
||||
return nil, fmt.Errorf("unable to get union reader: %w", err)
|
||||
}
|
||||
|
||||
exec, err := processExecutable(loc, uReader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
|
||||
}
|
||||
return exec
|
||||
return processExecutable(loc, uReader)
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
@@ -153,10 +153,12 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
@@ -165,16 +167,19 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err := findELFFeatures(&data, reader); err != nil {
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err := findPEFeatures(&data, reader); err != nil {
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err := findMachoFeatures(&data, reader); err != nil {
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +188,7 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
@@ -20,8 +21,8 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
|
||||
err = unknown.Joinf(err, "unable to read imported libraries from elf file: %w", err)
|
||||
libs = nil
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||
data.HasExports = elfHasExports(f)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||
@@ -62,7 +63,6 @@ func checkElfStackCanary(file *elf.File) *bool {
|
||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||
dynSyms, err := file.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -129,7 +129,6 @@ func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
|
||||
return false
|
||||
}
|
||||
@@ -144,7 +143,6 @@ func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
|
||||
return false
|
||||
}
|
||||
@@ -203,7 +201,6 @@ func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
||||
// look for any symbols that are functions and end with ".cfi"
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -225,7 +222,6 @@ var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
||||
func checkClangFortifySource(file *elf.File) *bool {
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -254,7 +250,7 @@ func elfHasExports(f *elf.File) bool {
|
||||
// really anything that is not marked with 'U' (undefined) is considered an export.
|
||||
symbols, err := f.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns?
|
||||
log.WithFields("error", err).Trace("unable to get ELF dynamic symbols")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ func Test_findELFSecurityFeatures(t *testing.T) {
|
||||
name string
|
||||
fixture string
|
||||
want *file.ELFSecurityFeatures
|
||||
wantErr require.ErrorAssertionFunc
|
||||
wantStripped bool
|
||||
}{
|
||||
{
|
||||
@@ -221,6 +222,7 @@ func Test_elfHasExports(t *testing.T) {
|
||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, elfHasExports(f))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
@@ -46,6 +47,7 @@ func NewCataloger(cfg Config) *Cataloger {
|
||||
func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) {
|
||||
results := make(map[file.Coordinates]string)
|
||||
var locations []file.Location
|
||||
var errs error
|
||||
|
||||
locations, err := resolver.FilesByGlob(i.globs...)
|
||||
if err != nil {
|
||||
@@ -59,8 +61,9 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
|
||||
metadata, err := resolver.FileMetadataByLocation(location)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, location, err)
|
||||
prog.SetError(err)
|
||||
return nil, err
|
||||
continue
|
||||
}
|
||||
|
||||
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
|
||||
@@ -69,12 +72,12 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
|
||||
result, err := i.catalogLocation(resolver, location)
|
||||
if internal.IsErrPathPermission(err) {
|
||||
log.Debugf("file contents cataloger skipping - %+v", err)
|
||||
errs = unknown.Append(errs, location, fmt.Errorf("permission error reading file contents: %w", err))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
prog.SetError(err)
|
||||
return nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog.Increment()
|
||||
@@ -87,7 +90,7 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
intCataloger "github.com/anchore/syft/syft/file/cataloger/internal"
|
||||
@@ -33,6 +34,7 @@ func NewCataloger(hashes []crypto.Hash) *Cataloger {
|
||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
|
||||
results := make(map[file.Coordinates][]file.Digest)
|
||||
var locations []file.Location
|
||||
var errs error
|
||||
|
||||
if len(coordinates) == 0 {
|
||||
locations = intCataloger.AllRegularFiles(ctx, resolver)
|
||||
@@ -58,12 +60,14 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
|
||||
if internal.IsErrPathPermission(err) {
|
||||
log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
prog.SetError(err)
|
||||
return nil, fmt.Errorf("failed to process file %q: %w", location.RealPath, err)
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog.Increment()
|
||||
@@ -76,7 +80,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
@@ -20,6 +21,7 @@ func NewCataloger() *Cataloger {
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
|
||||
var errs error
|
||||
results := make(map[file.Coordinates]file.Metadata)
|
||||
var locations <-chan file.Location
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -34,7 +36,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
for _, c := range coordinates {
|
||||
locs, err := resolver.FilesByPath(c.RealPath)
|
||||
if err != nil {
|
||||
log.Warn("unable to get file locations for path %q: %w", c.RealPath, err)
|
||||
errs = unknown.Append(errs, c, err)
|
||||
continue
|
||||
}
|
||||
for _, loc := range locs {
|
||||
@@ -71,7 +73,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
|
||||
@@ -39,3 +39,7 @@ func (c Coordinates) String() string {
|
||||
}
|
||||
return fmt.Sprintf("Location<%s>", str)
|
||||
}
|
||||
|
||||
func (c Coordinates) GetCoordinates() Coordinates {
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Unknowns: map[file.Coordinates][]string{},
|
||||
Executables: map[file.Coordinates]file.Executable{
|
||||
c: {
|
||||
Format: file.ELF,
|
||||
|
||||
@@ -13,6 +13,7 @@ type File struct {
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||
Executable *file.Executable `json:"executable,omitempty"`
|
||||
Unknowns []string `json:"unknowns,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
|
||||
@@ -101,6 +101,11 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
contents = contentsForLocation
|
||||
}
|
||||
|
||||
var unknowns []string
|
||||
if unknownsForLocation, exists := artifacts.Unknowns[coordinates]; exists {
|
||||
unknowns = unknownsForLocation
|
||||
}
|
||||
|
||||
var licenses []model.FileLicense
|
||||
for _, l := range artifacts.FileLicenses[coordinates] {
|
||||
var evidence *model.FileLicenseEvidence
|
||||
@@ -132,6 +137,7 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
Contents: contents,
|
||||
Licenses: licenses,
|
||||
Executable: executable,
|
||||
Unknowns: unknowns,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
|
||||
FileContents: fileArtifacts.FileContents,
|
||||
FileLicenses: fileArtifacts.FileLicenses,
|
||||
Executables: fileArtifacts.Executables,
|
||||
Unknowns: fileArtifacts.Unknowns,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
@@ -66,6 +67,7 @@ func deduplicateErrors(errors []error) []string {
|
||||
return errorMessages
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
ret := sbom.Artifacts{
|
||||
FileMetadata: make(map[file.Coordinates]file.Metadata),
|
||||
@@ -73,6 +75,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
FileContents: make(map[file.Coordinates]string),
|
||||
FileLicenses: make(map[file.Coordinates][]file.License),
|
||||
Executables: make(map[file.Coordinates]file.Executable),
|
||||
Unknowns: make(map[file.Coordinates][]string),
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
@@ -130,6 +133,10 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
if f.Executable != nil {
|
||||
ret.Executables[coord] = *f.Executable
|
||||
}
|
||||
|
||||
if len(f.Unknowns) > 0 {
|
||||
ret.Unknowns[coord] = f.Unknowns
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
@@ -234,6 +234,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{},
|
||||
Executables: map[file.Coordinates]file.Executable{},
|
||||
Unknowns: make(map[file.Coordinates][]string),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -349,6 +350,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.want.FileContents = make(map[file.Coordinates]string)
|
||||
tt.want.FileLicenses = make(map[file.Coordinates][]file.License)
|
||||
tt.want.Unknowns = make(map[file.Coordinates][]string)
|
||||
assert.Equal(t, tt.want, toSyftFiles(tt.files))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,12 +93,13 @@ func (ps pathSkipper) pathIndexVisitor(_ string, givenPath string, _ os.FileInfo
|
||||
for _, mi := range ps.mounts {
|
||||
conditionalPaths, ignorable := ps.ignorableMountTypes[mi.FSType]
|
||||
|
||||
if len(conditionalPaths) == 0 {
|
||||
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
|
||||
if !containsPath(givenPath, mi.Mountpoint) {
|
||||
continue
|
||||
}
|
||||
// Rule 0: Make sure the given path is within the mount point; if not let the scan continue
|
||||
if !containsPath(givenPath, mi.Mountpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
|
||||
if len(conditionalPaths) == 0 {
|
||||
if !ignorable {
|
||||
// we've matched on the most specific path at this point, which means we should stop searching
|
||||
// mount points for this path
|
||||
@@ -151,7 +152,7 @@ func simpleClean(p string) string {
|
||||
return "."
|
||||
}
|
||||
if p == "/" {
|
||||
return "/"
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSuffix(p, "/")
|
||||
}
|
||||
|
||||
@@ -358,6 +358,43 @@ func Test_newPathSkipper(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buildkit github ubuntu 22.04",
|
||||
root: "/run/src/core/sbom",
|
||||
mounts: []*mountinfo.Info{
|
||||
{Mountpoint: "/", FSType: "overlay"},
|
||||
{Mountpoint: "/proc", FSType: "proc"},
|
||||
{Mountpoint: "/dev", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/pts", FSType: "devpts"},
|
||||
{Mountpoint: "/dev/shm", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/mqueue", FSType: "mqueue"},
|
||||
{Mountpoint: "/sys", FSType: "sysfs"},
|
||||
{Mountpoint: "/etc/resolv.conf", FSType: "ext4"},
|
||||
{Mountpoint: "/etc/hosts", FSType: "ext4"},
|
||||
{Mountpoint: "/sys/fs/cgroup", FSType: "cgroup2"},
|
||||
{Mountpoint: "/run/out", FSType: "ext4"},
|
||||
{Mountpoint: "/run/src/core/sbom", FSType: "overlay"},
|
||||
{Mountpoint: "/tmp", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/otel-grpc.sock", FSType: "overlay"},
|
||||
{Mountpoint: "/proc/bus", FSType: "proc"},
|
||||
{Mountpoint: "/proc/fs", FSType: "proc"},
|
||||
{Mountpoint: "/proc/irq", FSType: "proc"},
|
||||
{Mountpoint: "/proc/sys", FSType: "proc"},
|
||||
{Mountpoint: "/proc/sysrq-trigger", FSType: "proc"},
|
||||
{Mountpoint: "/proc/acpi", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/kcore", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/keys", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/latency_stats", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/timer_list", FSType: "tmpfs"},
|
||||
{Mountpoint: "/sys/firmware", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/scsi", FSType: "tmpfs"},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
path: "/run/src/core/sbom",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -190,6 +190,14 @@ func TestApkDBCataloger(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func Test_corruptDb(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, "test-fixtures/corrupt").
|
||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.ApkDBEntry{}, "Files", "GitCommit", "Checksum")).
|
||||
WithError().
|
||||
TestCataloger(t, NewDBCataloger())
|
||||
}
|
||||
|
||||
func TestCatalogerDependencyTree(t *testing.T) {
|
||||
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
expected := map[string][]string{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -38,6 +39,7 @@ type parsedData struct {
|
||||
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
var errs error
|
||||
var apks []parsedData
|
||||
var currentEntry parsedData
|
||||
entryParsingInProgress := false
|
||||
@@ -81,10 +83,12 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
||||
field := parseApkField(line)
|
||||
if field == nil {
|
||||
log.Warnf("unable to parse field data from line %q", line)
|
||||
errs = unknown.Appendf(errs, reader, "unable to parse field data from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.name) == 0 {
|
||||
log.Warnf("failed to parse field name from line %q", line)
|
||||
errs = unknown.Appendf(errs, reader, "failed to parse field name from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.value) == 0 {
|
||||
@@ -131,7 +135,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, errs
|
||||
}
|
||||
|
||||
func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
this is a corrupt db
|
||||
|
||||
C:Q1v4QhLje3kWlC8DJj+ZfJTjlJRSU=
|
||||
P:alpine-baselayout-data
|
||||
V:3.2.0-r22
|
||||
A:x86_64
|
||||
S:11435
|
||||
I:73728
|
||||
o:alpine-baselayout
|
||||
t:1655134784
|
||||
c:cb70ca5c6d6db0399d2dd09189c5d57827bce5cd
|
||||
r:alpine-baselayout
|
||||
F:etc
|
||||
R:fstab
|
||||
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
|
||||
R:group
|
||||
Z:Q13K+olJg5ayzHSVNUkggZJXuB+9Y=
|
||||
R:hostname
|
||||
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
|
||||
R:hosts
|
||||
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
|
||||
R:inittab
|
||||
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
|
||||
R:modules
|
||||
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
|
||||
R:mtab
|
||||
a:0:0:777
|
||||
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
|
||||
R:passwd
|
||||
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
|
||||
R:profile
|
||||
Z:Q1F3DgXUP+jNZDknmQPPb5t9FSfDg=
|
||||
R:protocols
|
||||
Z:Q1omKlp3vgGq2ZqYzyD/KHNdo8rDc=
|
||||
R:services
|
||||
Z:Q19WLCv5ItKg4MH7RWfNRh1I7byQc=
|
||||
R:shadow
|
||||
a:0:42:640
|
||||
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
|
||||
R:shells
|
||||
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
|
||||
R:sysctl.conf
|
||||
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=
|
||||
@@ -11,6 +11,14 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestAlpmUnknowns(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, "test-fixtures/installed").
|
||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
|
||||
WithError().
|
||||
TestCataloger(t, NewDBCataloger())
|
||||
}
|
||||
|
||||
func TestAlpmCataloger(t *testing.T) {
|
||||
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -41,6 +42,8 @@ type parsedData struct {
|
||||
|
||||
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
|
||||
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
|
||||
data, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -52,24 +55,25 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
||||
|
||||
base := path.Dir(reader.RealPath)
|
||||
|
||||
var locs []file.Location
|
||||
|
||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||
// thus more useful.
|
||||
files, fileLoc := fetchPkgFiles(base, resolver)
|
||||
backups, backupLoc := fetchBackupFiles(base, resolver)
|
||||
|
||||
var locs []file.Location
|
||||
if fileLoc != nil {
|
||||
files, fileLoc, err := fetchPkgFiles(base, resolver)
|
||||
errs = unknown.Join(errs, err)
|
||||
if err == nil {
|
||||
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
data.Files = files
|
||||
}
|
||||
|
||||
if backupLoc != nil {
|
||||
backups, backupLoc, err := fetchBackupFiles(base, resolver)
|
||||
errs = unknown.Join(errs, err)
|
||||
if err == nil {
|
||||
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
data.Backup = backups
|
||||
}
|
||||
|
||||
if data.Package == "" {
|
||||
return nil, nil, nil
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
@@ -79,63 +83,56 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
locs...,
|
||||
),
|
||||
}, nil, nil
|
||||
}, nil, errs
|
||||
}
|
||||
|
||||
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||
// TODO: probably want to use MTREE and PKGINFO here
|
||||
target := path.Join(base, "mtree")
|
||||
|
||||
loc, err := getLocation(target, resolver)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find mtree file: %w", err))
|
||||
}
|
||||
if loc == nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(*loc)
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
pkgFiles, err := parseMtree(reader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse mtree: %w", err))
|
||||
}
|
||||
return pkgFiles, loc
|
||||
return pkgFiles, loc, nil
|
||||
}
|
||||
|
||||
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||
// We only really do this to get any backup database entries from the files database
|
||||
target := filepath.Join(base, "files")
|
||||
|
||||
loc, err := getLocation(target, resolver)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
}
|
||||
if loc == nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find alpm files: %w", err))
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(*loc)
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
filesMetadata, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse alpm db entry: %w", err))
|
||||
}
|
||||
if filesMetadata != nil {
|
||||
return filesMetadata.Backup, loc
|
||||
return filesMetadata.Backup, loc, nil
|
||||
}
|
||||
return []pkg.AlpmFileRecord{}, loc
|
||||
return []pkg.AlpmFileRecord{}, loc, nil
|
||||
}
|
||||
|
||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||
@@ -171,20 +168,21 @@ func newScanner(reader io.Reader) *bufio.Scanner {
|
||||
return scanner
|
||||
}
|
||||
|
||||
func getLocation(path string, resolver file.Resolver) (*file.Location, error) {
|
||||
func getLocation(path string, resolver file.Resolver) (file.Location, error) {
|
||||
loc := file.NewLocation(path)
|
||||
locs, err := resolver.FilesByPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return loc, err
|
||||
}
|
||||
|
||||
if len(locs) == 0 {
|
||||
return nil, fmt.Errorf("could not find file: %s", path)
|
||||
return loc, fmt.Errorf("could not find file: %s", path)
|
||||
}
|
||||
|
||||
if len(locs) > 1 {
|
||||
log.WithFields("path", path).Trace("multiple files found for path, using first path")
|
||||
}
|
||||
return &locs[0], nil
|
||||
return locs[0], nil
|
||||
}
|
||||
|
||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
%NME%
|
||||
tree-sitter
|
||||
|
||||
%VER.6-1
|
||||
|
||||
%BASE%
|
||||
tree-sitter
|
||||
|
||||
%DESC%
|
||||
Incremental parsing library
|
||||
|
||||
|
||||
%BUILDDATE%
|
||||
1714945746
|
||||
|
||||
%INSTALLDATE%
|
||||
1715026360
|
||||
|
||||
%PACKA@archlinux.org>
|
||||
|
||||
%SIZE%
|
||||
223539
|
||||
|
||||
%REASON%
|
||||
1
|
||||
|
||||
%LICENSE%
|
||||
MIT
|
||||
|
||||
%VALIDATION%
|
||||
pgp
|
||||
|
||||
%PROVIDE
|
||||
libtree-sitter.so=0-64
|
||||
@@ -6,8 +6,10 @@ package binary
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -59,12 +61,14 @@ func (c cataloger) Name() string {
|
||||
func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
var errs error
|
||||
|
||||
for _, cls := range c.classifiers {
|
||||
log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
|
||||
newPkgs, err := catalog(resolver, cls)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err)
|
||||
log.WithFields("error", err, "classifier", cls.Class).Debugf("unable to catalog binary package: %v", err)
|
||||
errs = unknown.Join(errs, fmt.Errorf("%s: %w", cls.Class, err))
|
||||
continue
|
||||
}
|
||||
newPackages:
|
||||
@@ -82,7 +86,7 @@ func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Pac
|
||||
}
|
||||
}
|
||||
|
||||
return packages, relationships, nil
|
||||
return packages, relationships, errs
|
||||
}
|
||||
|
||||
// mergePackages merges information from the extra package into the target package
|
||||
@@ -98,6 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
|
||||
}
|
||||
|
||||
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
|
||||
var errs error
|
||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -105,11 +110,12 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
|
||||
for _, location := range locations {
|
||||
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
packages = append(packages, pkgs...)
|
||||
}
|
||||
return packages, nil
|
||||
return packages, errs
|
||||
}
|
||||
|
||||
// packagesMatch returns true if the binary packages "match" based on basic criteria
|
||||
|
||||
@@ -348,6 +348,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("haproxy-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haproxy/2.0.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "haproxy",
|
||||
Version: "2.0.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/haproxy@2.0.0",
|
||||
Locations: locations("haproxy"),
|
||||
Metadata: metadata("haproxy-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haproxy/2.7.3/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -485,6 +496,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "redis-server/7.2.5/linux-386",
|
||||
expected: pkg.Package{
|
||||
Name: "redis",
|
||||
Version: "7.2.5",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/redis@7.2.5",
|
||||
Locations: locations("redis-server"),
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "python-shared-lib/3.7.4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -625,6 +647,36 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("go-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/0.10.48/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "0.10.48",
|
||||
PURL: "pkg:generic/node@0.10.48",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/0.12.18/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "0.12.18",
|
||||
PURL: "pkg:generic/node@0.12.18",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/4.9.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "4.9.1",
|
||||
PURL: "pkg:generic/node@4.9.1",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/19.2.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -850,6 +902,60 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.4.0-dev",
|
||||
logicalFixture: "ruby-shared-libs/3.4.0-dev/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.4.0dev",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.4.0dev",
|
||||
Locations: locations("ruby", "libruby.so.3.4.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.4.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.4.0-preview1",
|
||||
logicalFixture: "ruby-shared-libs/3.4.0-preview1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.4.0preview1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.4.0preview1",
|
||||
Locations: locations("ruby", "libruby.so.3.4.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.4.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.3.0-rc1",
|
||||
logicalFixture: "ruby-shared-libs/3.3.0-rc1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.3.0rc1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.3.0rc1",
|
||||
Locations: locations("ruby", "libruby.so.3.3.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.3.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
logicalFixture: "ruby-bullseye-shared-libs/2.7.7/linux-amd64",
|
||||
@@ -1562,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
|
||||
|
||||
resolver := &panicyResolver{}
|
||||
_, _, err := c.Catalog(context.Background(), resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, resolver.searchCalled)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func DefaultClassifiers() []Classifier {
|
||||
FileGlob: "**/redis-server",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(`(?s)payload %5.*?(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`),
|
||||
FileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`),
|
||||
FileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}\x00.*?payload %5`),
|
||||
),
|
||||
Package: "redis",
|
||||
PURL: mustPURL("pkg:generic/redis@version"),
|
||||
@@ -153,8 +153,14 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "nodejs-binary",
|
||||
FileGlob: "**/node",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
EvidenceMatcher: 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)\.[0-9]+\.[0-9]+)\x00`),
|
||||
FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
Package: "node",
|
||||
PURL: mustPURL("pkg:generic/node@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
@@ -190,6 +196,7 @@ 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`),
|
||||
),
|
||||
@@ -666,6 +673,9 @@ var libpythonMatcher = fileNameTemplateVersionMatcher(
|
||||
)
|
||||
|
||||
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[0-9]+)?) `)
|
||||
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
|
||||
|
||||
@@ -2,6 +2,7 @@ package binary
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -29,13 +30,15 @@ func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
os := metadata.OS
|
||||
osVersion := metadata.OSVersion
|
||||
|
||||
var atts cpe.Attributes
|
||||
atts, err := cpe.NewAttributes(metadata.OSCPE)
|
||||
if err == nil {
|
||||
// only "upgrade" the OS information if there is something more specific to use in it's place
|
||||
if os == "" && osVersion == "" || os == "" && atts.Version != "" || atts.Product != "" && osVersion == "" {
|
||||
os = atts.Product
|
||||
osVersion = atts.Version
|
||||
}
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse cpe attributes for elf binary package")
|
||||
}
|
||||
// only "upgrade" the OS information if there is something more specific to use in it's place
|
||||
if os == "" && osVersion == "" || os == "" && atts.Version != "" || atts.Product != "" && osVersion == "" {
|
||||
os = atts.Product
|
||||
osVersion = atts.Version
|
||||
}
|
||||
|
||||
if os != "" {
|
||||
|
||||
@@ -11,6 +11,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/internal/unionreader"
|
||||
@@ -52,6 +53,7 @@ func (c *elfPackageCataloger) Name() string {
|
||||
}
|
||||
|
||||
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err)
|
||||
@@ -62,7 +64,8 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
for _, location := range locations {
|
||||
notes, key, err := parseElfPackageNotes(resolver, location, c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
if notes == nil {
|
||||
continue
|
||||
@@ -87,7 +90,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
|
||||
// each binary. After all files and packages are processed there is a final task that creates package-to-package
|
||||
// and package-to-file relationships based on the dynamic libraries imported by each binary.
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, errs
|
||||
}
|
||||
|
||||
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
|
||||
@@ -104,7 +107,7 @@ func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elf
|
||||
|
||||
if err != nil {
|
||||
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
|
||||
return nil, elfPackageKey{}, nil
|
||||
return nil, elfPackageKey{}, err
|
||||
}
|
||||
|
||||
if notes == nil {
|
||||
@@ -173,7 +176,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
||||
if len(notes) > headerSize {
|
||||
var metadata elfBinaryPackageNotes
|
||||
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
|
||||
if err := json.Unmarshal(newPayload, &metadata); err == nil {
|
||||
if err = json.Unmarshal(newPayload, &metadata); err == nil {
|
||||
return &metadata, nil
|
||||
}
|
||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
||||
|
||||
@@ -3,6 +3,8 @@ package binary
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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,6 +16,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
name string
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
@@ -63,6 +66,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "fedora 64 bit binaries",
|
||||
@@ -116,6 +120,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
WithImageResolver(t, v.fixture).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(v.expected, nil).
|
||||
WithErrorAssertion(v.wantErr).
|
||||
TestCataloger(t, NewELFPackageCataloger())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -113,6 +113,18 @@ func Test_packageURL(t *testing.T) {
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
},
|
||||
{
|
||||
name: "bad or missing OSCPE data cannot be parsed allows for correct string",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
OSCPE: "%$#*(#*@&$(",
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -130,6 +130,13 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/sbin/haproxy
|
||||
|
||||
- version: 2.0.0
|
||||
images:
|
||||
- ref: haproxy:2.0.0@sha256:f88bca67a2782e7bd88af168d5c11216c32104d12fe9240fac54a1d3196e3f9c
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/sbin/haproxy
|
||||
|
||||
- version: 2.7.3
|
||||
images:
|
||||
- ref: haproxy:2.7.3@sha256:17d8aa6bf16882a294bdcccc757dd4002045f34b719e9f94dfd4801614801aea
|
||||
@@ -256,6 +263,27 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/openresty/nginx/sbin/nginx
|
||||
|
||||
- version: 0.10.48
|
||||
images:
|
||||
- ref: node:0.10.48@sha256:c32b4d56f05c69df6e87d06bf7d5f6a5c6a0e7bcdb8e5ffab0e7a1853a90008f
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/node
|
||||
|
||||
- version: 0.12.18
|
||||
images:
|
||||
- ref: node:0.12.18@sha256:02e8e9903c8d974e8874d0144413d398a41d62f54bafec4d2cf3ac2a8501dd28
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/node
|
||||
|
||||
- version: 4.9.1
|
||||
images:
|
||||
- ref: node:4.9.1@sha256:41d0ad2557ea2a9e57e1a458c1d659e92f601586e07dcffef74c9cef542f6f6e
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/node
|
||||
|
||||
- version: 19.2.0
|
||||
images:
|
||||
- ref: node:19.2.0@sha256:9bf5846b28f63acab0ccb0a39a245fbc414e6b7ecd467282f58016537c06e159
|
||||
@@ -440,6 +468,13 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/bin/redis-server
|
||||
|
||||
- version: 7.2.5
|
||||
images:
|
||||
- ref: redis:7.2.5@sha256:3b44455ef048b5f0a3a26d874c00c0f4ffe6a71a40834f2b038f816c6ad5ca4f
|
||||
platform: linux/386
|
||||
paths:
|
||||
- /usr/local/bin/redis-server
|
||||
|
||||
- version: 2.9.0
|
||||
images:
|
||||
- ref: wordpress:cli-2.9.0-php8.3@sha256:3a008aafccc6a8d2d7ec2b069722cb43b3569b61cd833f9f2f13d74a9284746e
|
||||
@@ -563,6 +598,36 @@ from-images:
|
||||
- /usr/local/lib/libruby.so.2.7.7
|
||||
- /usr/local/lib/libruby.so.2.7
|
||||
|
||||
- name: ruby-shared-libs
|
||||
version: 3.4.0-dev
|
||||
images:
|
||||
- ref: rubylang/ruby:master-dev-nightly-20240804-focal@sha256:b5fe8b9ac1e9638a4b08bda6a6343082a07623d1c01926e637eb4bbbbf159409
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/ruby
|
||||
- /usr/local/lib/libruby.so.3.4.0
|
||||
- /usr/local/lib/libruby.so.3.4
|
||||
|
||||
- name: ruby-shared-libs
|
||||
version: 3.4.0-preview1
|
||||
images:
|
||||
- ref: ruby:3.4.0-preview1@sha256:88cad31539e1a4ab987ac4b00c45b86d8346c25e3b08ac647ccccfb21cee5890
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/ruby
|
||||
- /usr/local/lib/libruby.so.3.4.0
|
||||
- /usr/local/lib/libruby.so.3.4
|
||||
|
||||
- name: ruby-shared-libs
|
||||
version: 3.3.0-rc1
|
||||
images:
|
||||
- ref: ruby:3.3.0-rc1@sha256:453c8bbb9b4e3b04b94ba58db77b648222f3c92936dc12b5ae96df48076c106b
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/ruby
|
||||
- /usr/local/lib/libruby.so.3.3.0
|
||||
- /usr/local/lib/libruby.so.3.3
|
||||
|
||||
- name: ruby-shared-libs
|
||||
version: 2.6.10
|
||||
images:
|
||||
|
||||
@@ -5,9 +5,11 @@ RUN dnf update -y; \
|
||||
dnf clean all
|
||||
RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib
|
||||
RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib
|
||||
RUN mkdir -p /usr/local/bin/elftests/elfbinwithcorrupt
|
||||
|
||||
COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib
|
||||
COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib
|
||||
COPY ./elfbinwithcorrupt /usr/local/bin/elftests/elfbinwithcorrupt
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib
|
||||
|
||||
@@ -16,6 +18,8 @@ RUN make
|
||||
|
||||
WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib
|
||||
RUN make
|
||||
WORKDIR /usr/local/bin/elftests/elfbinwithcorrupt
|
||||
RUN make
|
||||
|
||||
# let's make the test image smaller, since we only require the built binaries and supporting libraries
|
||||
FROM busybox:1.36.1-musl
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#include <iostream>
|
||||
#include "hello_world.h"
|
||||
|
||||
void print_hello_world() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
|
||||
#ifndef HELLO_WORLD_H
|
||||
#define HELLO_WORLD_H
|
||||
|
||||
// Function declaration for printing "Hello, World!" to stdout
|
||||
void print_hello_world();
|
||||
|
||||
#endif // HELLO_WORLD_H
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
LDFLAGS := -L/lib64 -lstdc++
|
||||
|
||||
SRC_DIR := ./
|
||||
BUILD_DIR := ../build
|
||||
BIN_DIR := ../bin
|
||||
LIB_DIR := $(BIN_DIR)/lib
|
||||
|
||||
LIB_NAME := hello_world
|
||||
LIB_SRC := $(SRC_DIR)/hello_world.cpp
|
||||
LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o
|
||||
LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so
|
||||
|
||||
EXECUTABLE := elfbinwithnestedlib
|
||||
EXEC_SRC := $(SRC_DIR)/testbin.cpp
|
||||
EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o
|
||||
|
||||
|
||||
|
||||
all: testfixture
|
||||
|
||||
$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR)
|
||||
$(CC) -shared -o $@ $<
|
||||
echo '{ corrupt json "system": "syftsys","name": "libhello_world.so","version": "0.01","pure:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
|
||||
|
||||
$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_DIR):
|
||||
mkdir -p $(BIN_DIR)
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
$(LIB_DIR):
|
||||
mkdir -p $(LIB_DIR)
|
||||
|
||||
$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR)
|
||||
$(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS)
|
||||
echo '{corrupt json ..._syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
|
||||
|
||||
testfixture: $(BIN_DIR)/$(EXECUTABLE)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#include "hello_world.h"
|
||||
|
||||
int main() {
|
||||
// Call the function from the shared library
|
||||
print_hello_world();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
CC = g++
|
||||
CFLAGS = -std=c++17 -Wall -Wextra -pedantic
|
||||
BUILD_DIR := ./build
|
||||
BIN_DIR := ./bin
|
||||
LIB_DIR := $(BIN_DIR)/lib
|
||||
|
||||
|
||||
|
||||
all: testfixtures
|
||||
|
||||
testfixtures:
|
||||
$(MAKE) -C elfsrc
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(BIN_DIR)
|
||||
|
||||
.PHONY: all clean testfixtures
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -25,7 +26,7 @@ func parseConanfile(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
||||
line, err := r.ReadString('\n')
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
case err != nil:
|
||||
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -72,7 +73,7 @@ func parseConanLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, relationships, nil
|
||||
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
// handleConanLockV1 handles the parsing of conan lock v1 files (aka v0.4)
|
||||
|
||||
@@ -373,3 +373,10 @@ func TestParseConanLockV2(t *testing.T) {
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parseConanLock, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_corruptConanlock(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/corrupt/conan.lock").
|
||||
WithError().
|
||||
TestParser(t, parseConanLock)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
corrupt json
|
||||
version": "0.5",
|
||||
"requires": [
|
||||
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
|
||||
"matrix/1.1#905c3f0babc520684c84127378fefdd0%1675278901.7527816"
|
||||
],
|
||||
"build_requires": [],
|
||||
"python_requires": []
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -85,7 +86,7 @@ func parsePubspecLock(_ context.Context, _ file.Resolver, _ *generic.Environment
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
func (p *pubspecLockPackage) getVcsURL() string {
|
||||
|
||||
@@ -106,3 +106,10 @@ func TestParsePubspecLock(t *testing.T) {
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_corruptPubspecLock(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/corrupt/pubspec.lock").
|
||||
WithError().
|
||||
TestParser(t, parsePubspecLock)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
pa
|
||||
kages:
|
||||
ale:
|
||||
dependency: transitive
|
||||
descr
|
||||
s ps:
|
||||
dart: ">=2.12.0 <3.0.0"
|
||||
@@ -25,15 +25,16 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkg-db-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL-1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
|
||||
),
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"),
|
||||
file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"),
|
||||
file.NewLocation("/var/lib/dpkg/status"),
|
||||
file.NewLocation("/var/lib/dpkg/info/libpam-runtime.preinst"),
|
||||
file.NewLocation("/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
file.NewLocation("/var/lib/dpkg/info/libpam-runtime.conffiles"),
|
||||
file.NewLocation("/usr/share/doc/libpam-runtime/copyright"),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
@@ -98,14 +99,15 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
Version: "3.34.1-3",
|
||||
FoundBy: "dpkg-db-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("public-domain", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2+", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("public-domain", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2+", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
|
||||
),
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0", "/var/lib/dpkg/status.d/libsqlite3-0"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0.md5sums", "/var/lib/dpkg/status.d/libsqlite3-0.md5sums"),
|
||||
file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright"),
|
||||
file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0"),
|
||||
file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0.md5sums"),
|
||||
file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0.preinst"),
|
||||
file.NewLocation("/usr/share/doc/libsqlite3-0/copyright"),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
|
||||
@@ -22,14 +22,18 @@ const (
|
||||
docsPath = "/usr/share/doc"
|
||||
)
|
||||
|
||||
func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release) pkg.Package {
|
||||
func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release, evidence ...file.Location) pkg.Package {
|
||||
// TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
|
||||
licenses := make([]pkg.License, 0)
|
||||
|
||||
locations := file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
locations.Add(evidence...)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Locations: locations,
|
||||
PURL: packageURL(d, release),
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: d,
|
||||
@@ -88,7 +92,7 @@ func packageURL(m pkg.DpkgDBEntry, distro *linux.Release) string {
|
||||
func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
|
||||
metadata, ok := p.Metadata.(pkg.DpkgDBEntry)
|
||||
if !ok {
|
||||
log.WithFields("package", p).Warn("unable to extract DPKG metadata to add licenses")
|
||||
log.WithFields("package", p).Trace("unable to extract DPKG metadata to add licenses")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -110,7 +114,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
|
||||
func mergeFileListing(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
|
||||
metadata, ok := p.Metadata.(pkg.DpkgDBEntry)
|
||||
if !ok {
|
||||
log.WithFields("package", p).Warn("unable to extract DPKG metadata to file listing")
|
||||
log.WithFields("package", p).Trace("unable to extract DPKG metadata to file listing")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -204,7 +208,7 @@ func fetchMd5Contents(resolver file.Resolver, dbLocation file.Location, m pkg.Dp
|
||||
// this is unexpected, but not a show-stopper
|
||||
md5Reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err)
|
||||
log.Tracef("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
|
||||
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
|
||||
@@ -239,7 +243,7 @@ func fetchConffileContents(resolver file.Resolver, dbLocation file.Location, m p
|
||||
// this is unexpected, but not a show-stopper
|
||||
reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err)
|
||||
log.Tracef("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
|
||||
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
|
||||
@@ -263,7 +267,7 @@ func fetchCopyrightContents(resolver file.Resolver, dbLocation file.Location, m
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb copyright contents (package=%s): %s", m.Package, err)
|
||||
log.Tracef("failed to fetch deb copyright contents (package=%s): %s", m.Package, err)
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, location.RealPath)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -34,10 +36,39 @@ func parseDpkgDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, m := range metadata {
|
||||
pkgs = append(pkgs, newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease))
|
||||
p := newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease, findDpkgInfoFiles(m.Package, resolver, reader.Location)...)
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location {
|
||||
if resolver == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// for typical debian-base distributions, the installed package info is at /var/lib/dpkg/status
|
||||
// and the md5sum information is under /var/lib/dpkg/info/; however, for distroless the installed
|
||||
// package info is across multiple files under /var/lib/dpkg/status.d/ and the md5sums are contained in
|
||||
// the same directory
|
||||
searchPath := path.Dir(dbLocation.RealPath)
|
||||
|
||||
if !strings.HasSuffix(searchPath, "status.d") {
|
||||
searchPath = path.Join(searchPath, "info")
|
||||
}
|
||||
|
||||
// look for /var/lib/dpkg/info/NAME.*
|
||||
locations, err := resolver.FilesByGlob(path.Join(searchPath, name+".*"))
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "pkg", name).Trace("failed to fetch related dpkg info files")
|
||||
return nil
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
|
||||
|
||||
@@ -257,6 +257,17 @@ func Test_parseDpkgStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_corruptEntry(t *testing.T) {
|
||||
f, err := os.Open("test-fixtures/var/lib/dpkg/status.d/corrupt")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, f.Close()) })
|
||||
|
||||
reader := bufio.NewReader(f)
|
||||
|
||||
_, err = parseDpkgStatus(reader)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSourceVersionExtract(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -312,7 +323,7 @@ func Test_parseDpkgStatus_negativeCases(t *testing.T) {
|
||||
{
|
||||
name: "no more packages",
|
||||
input: `Package: apt`,
|
||||
wantErr: require.NoError,
|
||||
wantErr: requireAs(errors.New("unable to determine packages")),
|
||||
},
|
||||
{
|
||||
name: "duplicated key",
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
# some shell script...
|
||||
+1
@@ -0,0 +1 @@
|
||||
# some shell script...
|
||||
@@ -0,0 +1,6 @@
|
||||
Pakij: apt
|
||||
Stratus: install ok installed
|
||||
Prioority: required
|
||||
Section: admin
|
||||
Insterface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -126,5 +127,5 @@ func parseDotnetDeps(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
||||
// this will only consider package-to-package relationships.
|
||||
relationship.Sort(relationships)
|
||||
|
||||
return pkgs, relationships, nil
|
||||
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,13 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_corruptDotnetDeps(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/glob-paths/src/something.deps.json").
|
||||
WithError().
|
||||
TestParser(t, parseDotnetDeps)
|
||||
}
|
||||
|
||||
func TestParseDotnetDeps(t *testing.T) {
|
||||
fixture := "test-fixtures/TestLibrary.deps.json"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
@@ -28,30 +28,26 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
|
||||
|
||||
peFile, err := pe.NewBytes(by, &pe.Options{})
|
||||
if err != nil {
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = peFile.Parse()
|
||||
if err != nil {
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
versionResources, err := peFile.ParseVersionResources()
|
||||
if err != nil {
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
return nil, nil, fmt.Errorf("unable to parse version resources in PE file: %w", err)
|
||||
}
|
||||
|
||||
dotNetPkg, err := buildDotNetPackage(versionResources, f)
|
||||
if err != nil {
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to build dotnet package: %v", err)
|
||||
return nil, nil, nil
|
||||
log.Tracef("unable to build dotnet package for: %v %v", f.RealPath, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return []pkg.Package{dotNetPkg}, nil, nil
|
||||
@@ -60,12 +56,12 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
|
||||
func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) {
|
||||
name := findName(versionResources)
|
||||
if name == "" {
|
||||
return dnpkg, fmt.Errorf("unable to find PE name in file: %s", f.RealPath)
|
||||
return dnpkg, fmt.Errorf("unable to find PE name in file")
|
||||
}
|
||||
|
||||
version := findVersion(versionResources)
|
||||
if version == "" {
|
||||
return dnpkg, fmt.Errorf("unable to find PE version in file: %s", f.RealPath)
|
||||
return dnpkg, fmt.Errorf("unable to find PE version in file")
|
||||
}
|
||||
|
||||
metadata := pkg.DotnetPortableExecutableEntry{
|
||||
|
||||
@@ -297,6 +297,13 @@ func TestParseDotnetPortableExecutable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_corruptDotnetPE(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/glob-paths/src/something.exe").
|
||||
WithError().
|
||||
TestParser(t, parseDotnetPortableExecutable)
|
||||
}
|
||||
|
||||
func Test_extractVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -22,25 +23,33 @@ var mixLockDelimiter = regexp.MustCompile(`[%{}\n" ,:]+`)
|
||||
|
||||
// parseMixLock parses a mix.lock and returns the discovered Elixir packages.
|
||||
func parseMixLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
r := bufio.NewReader(reader)
|
||||
|
||||
var packages []pkg.Package
|
||||
lineNum := 0
|
||||
for {
|
||||
lineNum++
|
||||
line, err := r.ReadString('\n')
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
return packages, nil, nil
|
||||
if errs == nil {
|
||||
errs = unknown.IfEmptyf(packages, "unable to determine packages")
|
||||
}
|
||||
return packages, nil, errs
|
||||
case err != nil:
|
||||
return nil, nil, fmt.Errorf("failed to parse mix.lock file: %w", err)
|
||||
}
|
||||
tokens := mixLockDelimiter.Split(line, -1)
|
||||
if len(tokens) < 6 {
|
||||
errs = unknown.Appendf(errs, reader, "unable to read mix lock line %d: %s", lineNum, line)
|
||||
continue
|
||||
}
|
||||
name, version, hash, hashExt := tokens[1], tokens[4], tokens[5], tokens[len(tokens)-2]
|
||||
|
||||
if name == "" {
|
||||
log.WithFields("path", reader.RealPath).Debug("skipping empty package name from mix.lock file")
|
||||
errs = unknown.Appendf(errs, reader, "skipping empty package name from mix.lock file, for line: %d: %s", lineNum, line)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package erlang
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -17,7 +18,7 @@ func parseOTPApp(_ context.Context, _ file.Resolver, _ *generic.Environment, rea
|
||||
// there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all
|
||||
// ... which means we should not return an error here
|
||||
log.WithFields("error", err).Trace("unable to parse Erlang OTP app")
|
||||
return nil, nil, nil
|
||||
return nil, nil, fmt.Errorf("unable to parse Erlang OTP app")
|
||||
}
|
||||
|
||||
var packages []pkg.Package
|
||||
|
||||
@@ -41,3 +41,10 @@ func TestParseOTPApplication(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_corruptOtpApp(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/corrupt/rabbitmq.app").
|
||||
WithError().
|
||||
TestParser(t, parseOTPApp)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -95,7 +96,7 @@ func parseRebarLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
||||
p.SetID()
|
||||
packages = append(packages, *p)
|
||||
}
|
||||
return packages, nil, nil
|
||||
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
|
||||
}
|
||||
|
||||
// integrity check
|
||||
|
||||
@@ -255,3 +255,10 @@ func TestParseRebarLock(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_corruptRebarLock(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/corrupt/rebar.lock").
|
||||
WithError().
|
||||
TestParser(t, parseRebarLock)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cation, 'rabbit', [
|
||||
{description, "RabbitMQ"},
|
||||
{vsn, "3.12.10"},
|
||||
{id, "v3.12.9-9-g1f61ca8"},
|
||||
{modules, ['amqqueue','background_gc']},
|
||||
{optional_itmq-server#1593
|
||||
{channel_max, 2047}
|
||||
]}
|
||||
]}.
|
||||
@@ -0,0 +1,11 @@
|
||||
{"1.2.0",
|
||||
[{<<"certifi{pkg,<<"certifi">>,<<"2.9.0">>},0},
|
||||
{<<"idna">>,{pkg,<<"idpkg,<<"parse_trans">>,<<"3.3.1">>},0},
|
||||
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},0},
|
||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"certifi">>, <<"6F2A475689DD47F19FB74334859D460A2DC4E3252A3324BD2111B8F0429E7E21">>},
|
||||
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
|
||||
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
|
||||
{<<"mimerl
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/go-logger"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -151,6 +152,7 @@ func (c *Cataloger) Name() string {
|
||||
func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
var errs error
|
||||
|
||||
logger := log.Nested("cataloger", c.upstreamCataloger)
|
||||
|
||||
@@ -166,7 +168,8 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
|
||||
|
||||
discoveredPackages, discoveredRelationships, err := invokeParser(ctx, resolver, location, logger, parser, &env)
|
||||
if err != nil {
|
||||
continue // logging is handled within invokeParser
|
||||
// parsers may return errors and valid packages / relationships
|
||||
errs = unknown.Append(errs, location, err)
|
||||
}
|
||||
|
||||
for _, p := range discoveredPackages {
|
||||
@@ -176,7 +179,7 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
|
||||
|
||||
relationships = append(relationships, discoveredRelationships...)
|
||||
}
|
||||
return c.process(ctx, resolver, packages, relationships, nil)
|
||||
return c.process(ctx, resolver, packages, relationships, errs)
|
||||
}
|
||||
|
||||
func (c *Cataloger) process(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
@@ -196,11 +199,11 @@ func invokeParser(ctx context.Context, resolver file.Resolver, location file.Loc
|
||||
|
||||
discoveredPackages, discoveredRelationships, err := parser(ctx, resolver, env, file.NewLocationReadCloser(location, contentReader))
|
||||
if err != nil {
|
||||
logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed")
|
||||
return nil, nil, err
|
||||
// these errors are propagated up, and are likely to be coordinate errors
|
||||
logger.WithFields("location", location.RealPath, "error", err).Trace("cataloger returned errors")
|
||||
}
|
||||
|
||||
return discoveredPackages, discoveredRelationships, nil
|
||||
return discoveredPackages, discoveredRelationships, err
|
||||
}
|
||||
|
||||
// selectFiles takes a set of file trees and resolves and file references of interest for future cataloging
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -187,3 +188,27 @@ func TestClosesFileOnParserPanic(t *testing.T) {
|
||||
})
|
||||
require.True(t, spy.closed)
|
||||
}
|
||||
|
||||
func Test_genericCatalogerReturnsErrors(t *testing.T) {
|
||||
genericErrorReturning := NewCataloger("error returning").WithParserByGlobs(func(ctx context.Context, resolver file.Resolver, environment *Environment, locationReader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
return []pkg.Package{
|
||||
{
|
||||
Name: "some-package-" + locationReader.Path(),
|
||||
},
|
||||
}, nil, unknown.Newf(locationReader, "unable to read")
|
||||
}, "**/*")
|
||||
|
||||
m := file.NewMockResolverForPaths(
|
||||
"test-fixtures/a-path.txt",
|
||||
"test-fixtures/empty.txt",
|
||||
)
|
||||
|
||||
got, _, errs := genericErrorReturning.Catalog(context.TODO(), m)
|
||||
|
||||
// require packages and errors
|
||||
require.NotEmpty(t, got)
|
||||
|
||||
unknowns, others := unknown.ExtractCoordinateErrors(errs)
|
||||
require.NotEmpty(t, unknowns)
|
||||
require.Empty(t, others)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user