mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 05:52:25 +01:00
Compare commits
37
Commits
+3
-4
@@ -1,5 +1,4 @@
|
||||
tools:
|
||||
|
||||
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
|
||||
- name: binny
|
||||
version:
|
||||
@@ -43,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.2.2
|
||||
want: v2.2.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -59,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v1.23.0
|
||||
want: v1.24.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -112,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.42.1
|
||||
want: v2.43.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -176,7 +176,8 @@ def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
|
||||
|
||||
def list_json_schema_files() -> list[str]:
|
||||
# list files in "schema/json" directory matching the pattern of "schema-*.json"
|
||||
return sort_json_schema_files(list(glob.glob("schema/json/schema-*.json")))
|
||||
# special case: always ignore the "latest" schema file
|
||||
return sort_json_schema_files([f for f in glob.glob("schema/json/schema-*.json") if "latest" not in f])
|
||||
|
||||
|
||||
def run(command: str, **kwargs) -> subprocess.CompletedProcess:
|
||||
@@ -197,7 +198,7 @@ def sort_json_schema_files(files: list[str]) -> list[str]:
|
||||
# so that "schema/json/schema-1.2.1.json" comes before "schema/json/schema-1.12.1.json".
|
||||
versions = [get_semver(file) for file in files if file]
|
||||
|
||||
versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.')])
|
||||
versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.') if "." in s])
|
||||
|
||||
return [f"schema/json/schema-{version}.json" for version in versions]
|
||||
|
||||
|
||||
@@ -60,6 +60,11 @@ class Labeler(unittest.TestCase):
|
||||
expected_sorted_files = ["schema/json/schema-1.2.1.json", "schema/json/schema-1.12.1.json"]
|
||||
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
|
||||
|
||||
# ensure that "latest" doesn't cause a problem and is ultimately ignored
|
||||
files = ["schema/json/schema-1.12.1.json", "schema/json/schema-_bogus.json"]
|
||||
expected_sorted_files = ["schema/json/schema-_bogus.json", "schema/json/schema-1.12.1.json"]
|
||||
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
- name: Update PR benchmark results comment
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
header: benchmark
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 #v3.24.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 #v3.24.0
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 #v3.23.2
|
||||
uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 #v3.24.0
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Delete existing comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
hide: true
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- name: Add comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
path: ${{ env.CI_COMMENT_FILE }}
|
||||
|
||||
@@ -94,6 +94,8 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
with:
|
||||
@@ -141,7 +143,7 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- uses: anchore/sbom-action@c6aed38a4323b393d05372c58a74c39ae8386d02 #v0.15.6
|
||||
- uses: anchore/sbom-action@b6a39da80722a2cb0ef5d197531764a89b5d48c3 #v0.15.8
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 #v6.0.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 #v6.0.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
|
||||
- uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 #v6.0.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -36,6 +36,12 @@ jobs:
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore file executable test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
with:
|
||||
path: syft/file/cataloger/executable/test-fixtures/bin
|
||||
key: ${{ runner.os }}-unit-file-executable-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0
|
||||
with:
|
||||
|
||||
@@ -548,7 +548,7 @@ catalogers:
|
||||
default-catalogers: []
|
||||
|
||||
# add, remove, and filter the catalogers to be used
|
||||
# SYFT_SELECT_CATALOGERS env var / --select-cataloger flag;
|
||||
# SYFT_SELECT_CATALOGERS env var / --select-catalogers flag;
|
||||
select-catalogers: []
|
||||
|
||||
# all format configuration
|
||||
|
||||
+9
-5
@@ -266,6 +266,7 @@ tasks:
|
||||
desc: Generate test fixture fingerprints
|
||||
generates:
|
||||
- cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint
|
||||
- syft/file/cataloger/executable/test-fixtures/cache.fingerprint
|
||||
- syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint
|
||||
- syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint
|
||||
- syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint
|
||||
@@ -274,17 +275,19 @@ tasks:
|
||||
- test/install/cache.fingerprint
|
||||
- test/cli/test-fixtures/cache.fingerprint
|
||||
cmds:
|
||||
# for EXECUTABLE unit test fixtures
|
||||
- "cd syft/file/cataloger/executable/test-fixtures && make cache.fingerprint"
|
||||
# for IMAGE integration test fixtures
|
||||
- "cd cmd/syft/internal/test/integration/test-fixtures && make cache.fingerprint"
|
||||
# for BINARY test fixtures
|
||||
# for BINARY unit test fixtures
|
||||
- "cd syft/pkg/cataloger/binary/test-fixtures && make cache.fingerprint"
|
||||
# for JAVA BUILD test fixtures
|
||||
# for JAVA BUILD unit test fixtures
|
||||
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cache.fingerprint"
|
||||
# for GO BINARY test fixtures
|
||||
# for GO BINARY unit test fixtures
|
||||
- "cd syft/pkg/cataloger/golang/test-fixtures/archs && make binaries.fingerprint"
|
||||
# for RPM test fixtures
|
||||
# for RPM unit test fixtures
|
||||
- "cd syft/pkg/cataloger/redhat/test-fixtures && make rpms.fingerprint"
|
||||
# for Kernel test fixtures
|
||||
# for Kernel unit test fixtures
|
||||
- "cd syft/pkg/cataloger/kernel/test-fixtures && make cache.fingerprint"
|
||||
# for INSTALL test fixtures
|
||||
- "cd test/install && make cache.fingerprint"
|
||||
@@ -294,6 +297,7 @@ tasks:
|
||||
fixtures:
|
||||
desc: Generate test fixtures
|
||||
cmds:
|
||||
- "cd syft/file/cataloger/executable/test-fixtures && make"
|
||||
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make"
|
||||
- "cd syft/pkg/cataloger/redhat/test-fixtures && make"
|
||||
- "cd syft/pkg/cataloger/binary/test-fixtures && make"
|
||||
|
||||
+2
-44
@@ -8,13 +8,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
handler "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
"github.com/anchore/syft/cmd/syft/internal"
|
||||
"github.com/anchore/syft/cmd/syft/internal/commands"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
// Application constructs the `syft packages` command and aliases the root command to `syft packages`.
|
||||
@@ -34,44 +29,7 @@ func Command(id clio.Identification) *cobra.Command {
|
||||
}
|
||||
|
||||
func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
|
||||
clioCfg := clio.NewSetupConfig(id).
|
||||
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
|
||||
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
|
||||
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
handler.New(handler.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
}, nil
|
||||
},
|
||||
).
|
||||
WithInitializers(
|
||||
func(state *clio.State) error {
|
||||
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
|
||||
// we can hoist them into the internal packages for global use.
|
||||
stereoscope.SetBus(state.Bus)
|
||||
bus.Set(state.Bus)
|
||||
|
||||
redact.Set(state.RedactStore)
|
||||
|
||||
log.Set(state.Logger)
|
||||
stereoscope.SetLogger(state.Logger)
|
||||
|
||||
return nil
|
||||
},
|
||||
).
|
||||
WithPostRuns(func(state *clio.State, err error) {
|
||||
stereoscope.Cleanup()
|
||||
})
|
||||
clioCfg := internal.AppClioSetupConfig(id, out)
|
||||
|
||||
app := clio.New(*clioCfg)
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
ui2 "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig {
|
||||
clioCfg := clio.NewSetupConfig(id).
|
||||
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
|
||||
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
|
||||
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
ui2.New(ui2.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
}, nil
|
||||
},
|
||||
).
|
||||
WithInitializers(
|
||||
func(state *clio.State) error {
|
||||
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
|
||||
// we can hoist them into the internal packages for global use.
|
||||
stereoscope.SetBus(state.Bus)
|
||||
bus.Set(state.Bus)
|
||||
|
||||
redact.Set(state.RedactStore)
|
||||
|
||||
log.Set(state.Logger)
|
||||
stereoscope.SetLogger(state.Logger)
|
||||
return nil
|
||||
},
|
||||
).
|
||||
WithPostRuns(func(state *clio.State, err error) {
|
||||
stereoscope.Cleanup()
|
||||
})
|
||||
return clioCfg
|
||||
}
|
||||
@@ -42,7 +42,7 @@ type attestOptions struct {
|
||||
options.Output `yaml:",inline" mapstructure:",squash"`
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
options.Catalog `yaml:",inline" mapstructure:",squash"`
|
||||
options.Attest `yaml:",inline" mapstructure:",squash"`
|
||||
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
|
||||
}
|
||||
|
||||
func Attest(app clio.Application) *cobra.Command {
|
||||
|
||||
@@ -4,16 +4,21 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/clio/cliotestutils"
|
||||
"github.com/anchore/syft/cmd/syft/internal"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -267,3 +272,60 @@ func Test_buildSBOMForAttestation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_attestCLIWiring(t *testing.T) {
|
||||
id := clio.Identification{
|
||||
Name: "syft",
|
||||
Version: "testing",
|
||||
}
|
||||
cfg := internal.AppClioSetupConfig(id, io.Discard)
|
||||
tests := []struct {
|
||||
name string
|
||||
assertionFunc func(*testing.T, *cobra.Command, []string, ...any)
|
||||
wantOpts attestOptions
|
||||
args []string
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
name: "key flag is accepted",
|
||||
args: []string{"some-image:some-tag", "--key", "some-cosign-key.key"},
|
||||
assertionFunc: hasAttestOpts(options.Attest{Key: "some-cosign-key.key"}),
|
||||
},
|
||||
{
|
||||
name: "key password is read from env",
|
||||
args: []string{"some-image:some-tag", "--key", "cosign.key"},
|
||||
env: map[string]string{
|
||||
"SYFT_ATTEST_PASSWORD": "some-password",
|
||||
},
|
||||
assertionFunc: hasAttestOpts(options.Attest{
|
||||
Key: "cosign.key",
|
||||
Password: "some-password",
|
||||
}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != nil {
|
||||
for k, v := range tt.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
app := cliotestutils.NewApplication(t, cfg, tt.assertionFunc)
|
||||
cmd := Attest(app)
|
||||
cmd.SetArgs(tt.args)
|
||||
err := cmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func hasAttestOpts(wantOpts options.Attest) cliotestutils.AssertionFunc {
|
||||
return func(t *testing.T, _ *cobra.Command, _ []string, cfgs ...any) {
|
||||
assert.Equal(t, len(cfgs), 1)
|
||||
attestOpts, ok := cfgs[0].(*attestOptions)
|
||||
require.True(t, ok)
|
||||
if d := cmp.Diff(wantOpts, attestOpts.Attest); d != "" {
|
||||
t.Errorf("mismatched attest options (-want +got):\n%s", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
gologgerredact "github.com/anchore/go-logger/adapter/redact"
|
||||
"github.com/anchore/syft/internal/redact"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Initialize global state needed to test clio/cobra commands directly
|
||||
// Should be kept minimal.
|
||||
|
||||
// Initialize redact store once for all tests in the commands package
|
||||
// Redact store must be wired up here because syft will panic unless
|
||||
// a redact store is wired up exactly once
|
||||
redact.Set(gologgerredact.NewStore())
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
)
|
||||
|
||||
var _ clio.FlagAdder = (*Attest)(nil)
|
||||
|
||||
type Attest struct {
|
||||
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
|
||||
Key secret `yaml:"key" json:"key" mapstructure:"key"`
|
||||
@@ -12,6 +14,6 @@ type Attest struct {
|
||||
|
||||
var _ clio.FlagAdder = (*Attest)(nil)
|
||||
|
||||
func (o Attest) AddFlags(flags clio.FlagSet) {
|
||||
func (o *Attest) AddFlags(flags clio.FlagSet) {
|
||||
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
@@ -111,6 +112,10 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
Globs: cfg.File.Content.Globs,
|
||||
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
|
||||
},
|
||||
Executable: executable.Config{
|
||||
MIMETypes: executable.DefaultConfig().MIMETypes,
|
||||
Globs: cfg.File.Executable.Globs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type fileConfig struct {
|
||||
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
|
||||
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
|
||||
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Executable fileExecutable `yaml:"executable" json:"executable" mapstructure:"executable"`
|
||||
}
|
||||
|
||||
type fileMetadata struct {
|
||||
@@ -25,6 +26,10 @@ type fileContent struct {
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type fileExecutable struct {
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
func defaultFileConfig() fileConfig {
|
||||
return fileConfig{
|
||||
Metadata: fileMetadata{
|
||||
@@ -34,6 +39,9 @@ func defaultFileConfig() fileConfig {
|
||||
Content: fileContent{
|
||||
SkipFilesAboveSize: 250 * intFile.KB,
|
||||
},
|
||||
Executable: fileExecutable{
|
||||
Globs: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +368,14 @@ var dirOnlyTestCases = []testCase{
|
||||
"unicode_util_compat": "0.7.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find ErLang OTP applications",
|
||||
pkgType: pkg.ErlangOTPPkg,
|
||||
pkgLanguage: pkg.Erlang,
|
||||
pkgInfo: map[string]string{
|
||||
"accept": "0.3.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find swift package manager packages",
|
||||
pkgType: pkg.SwiftPkg,
|
||||
|
||||
@@ -57,8 +57,8 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedLanguages.Remove(pkg.Swift.String())
|
||||
definedLanguages.Remove(pkg.CPP.String())
|
||||
definedLanguages.Remove(pkg.Haskell.String())
|
||||
definedLanguages.Remove(pkg.Erlang.String())
|
||||
definedLanguages.Remove(pkg.Elixir.String())
|
||||
definedLanguages.Remove(pkg.Erlang.String())
|
||||
|
||||
observedPkgs := strset.New()
|
||||
definedPkgs := strset.New()
|
||||
@@ -71,6 +71,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.GoModulePkg))
|
||||
definedPkgs.Remove(string(pkg.RustPkg))
|
||||
definedPkgs.Remove(string(pkg.DartPubPkg))
|
||||
definedPkgs.Remove(string(pkg.ErlangOTPPkg))
|
||||
definedPkgs.Remove(string(pkg.CocoapodsPkg))
|
||||
definedPkgs.Remove(string(pkg.ConanPkg))
|
||||
definedPkgs.Remove(string(pkg.HackagePkg))
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGolangCompilerDetection(t *testing.T) {
|
||||
name: "syft can detect a single golang compiler given the golang base image",
|
||||
image: "image-golang-compiler",
|
||||
expectedCompilers: []string{"go1.18.10"},
|
||||
expectedCPE: []cpe.CPE{cpe.Must("cpe:2.3:a:golang:go:1.18.10:-:*:*:*:*:*:*")},
|
||||
expectedCPE: []cpe.CPE{cpe.Must("cpe:2.3:a:golang:go:1.18.10:-:*:*:*:*:*:*", cpe.GeneratedSource)},
|
||||
expectedPURL: []string{"pkg:golang/stdlib@1.18.10"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ var noAssertion = map[string]string{
|
||||
"/packages/TwilioNotifier.hpi:WEB-INF/lib/sdk-3.0.jar": "pkg:maven/sdk/sdk@3.0",
|
||||
|
||||
// syft generates incorrect purls
|
||||
"/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/akka-actor_2.13-2.6.6.jar": "pkg:maven/com.typesafe.akka/akka-actor_2.13@2.6.6",
|
||||
"/packages/akka-management-cluster-bootstrap_2.13-1.2.0.jar": "pkg:maven/com.lightbend.akka.management/akka-management-cluster-bootstrap_2.13@1.2.0",
|
||||
"/packages/hudson.war:WEB-INF/lib/asm-2.2.3.jar": "pkg:maven/asm/asm@2.2.3",
|
||||
@@ -366,10 +365,10 @@ var expectedPURLs = map[string]string{
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/org/yaml/snakeyaml/1.18/snakeyaml-1.18.jar": "pkg:maven/org.yaml/snakeyaml@1.18",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/readline.jar:jline:jline": "pkg:maven/jline/jline@2.11",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar:META-INF/jruby.home/lib/ruby/stdlib/readline.jar:rubygems:jruby-readline": "pkg:maven/rubygems/jruby-readline@1.2.0",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar": "pkg:maven/org.jruby/jruby-stdlib@9.1.15.0",
|
||||
"/packages/jsch-0.1.55.jar": "pkg:maven/com.jcraft/jsch@0.1.55",
|
||||
"/packages/junit-4.13.1.jar": "pkg:maven/junit/junit@4.13.1",
|
||||
// "/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/jruby-stdlib-9.1.15.0.jar": "pkg:maven/org.jruby/jruby-stdlib@9.1.15.0",
|
||||
"/packages/jsch-0.1.55.jar": "pkg:maven/com.jcraft/jsch@0.1.55",
|
||||
"/packages/junit-4.13.1.jar": "pkg:maven/junit/junit@4.13.1",
|
||||
"/packages/kafka_2.13-3.2.2.jar": "pkg:maven/org.apache.kafka/kafka_2.13@3.2.2",
|
||||
"/packages/keycloak-core-22.0.2.jar": "pkg:maven/org.keycloak/keycloak-core@22.0.2",
|
||||
"/packages/log4j-1.2.16.jar": "pkg:maven/log4j/log4j@1.2.16",
|
||||
"/packages/log4j-core-2.17.0.jar": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0",
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{application,accept,
|
||||
[{description,"Accept header(s) for Erlang/Elixir"},
|
||||
{vsn,"0.3.5"},
|
||||
{registered,[]},
|
||||
{applications,[kernel,stdlib]},
|
||||
{env,[]},
|
||||
{modules, ['accept_encoding_header','accept_header','accept_neg','accept_parser']},
|
||||
{maintainers,["Ilya Khaprov"]},
|
||||
{licenses,["MIT"]},
|
||||
{links,[{"Github","https://github.com/deadtrickster/accept"}]}]}.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Syft API Examples
|
||||
|
||||
This directory contains examples of how to use the Syft API.
|
||||
|
||||
- `create_simple_sbom`: Create a simple SBOM from scratch
|
||||
- `create_custom_sbom`: Create an SBOM using as much custom configuration as possible, including a custom cataloger implementation
|
||||
- `decode_sbom`: Take an existing SBOM file (of arbitrary format) and decode it into a Syft SBOM object
|
||||
- `source_detection`: Shows how to detect what to catalog automatically from a user string (e.g. container image vs directory)
|
||||
- `source_from_image`: Construct a source from a only a container image
|
||||
|
||||
You can run any of these examples from this directory with:
|
||||
|
||||
```bash
|
||||
go run ./DIRECTORY_NAME
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
/*
|
||||
This is a contrived cataloger that attempts to capture useful APK files from the image as if it were a package.
|
||||
This isn't a real cataloger, but it is a good example of how to use API elements to create a custom cataloger.
|
||||
*/
|
||||
|
||||
var _ pkg.Cataloger = (*alpineConfigurationCataloger)(nil)
|
||||
|
||||
type alpineConfigurationCataloger struct {
|
||||
}
|
||||
|
||||
func newAlpineConfigurationCataloger() pkg.Cataloger {
|
||||
return alpineConfigurationCataloger{}
|
||||
}
|
||||
|
||||
func (m alpineConfigurationCataloger) Name() string {
|
||||
return "apk-configuration-cataloger"
|
||||
}
|
||||
|
||||
func (m alpineConfigurationCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
version, versionLocations, err := getVersion(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get alpine version: %w", err)
|
||||
}
|
||||
if len(versionLocations) == 0 {
|
||||
// this doesn't mean we should stop cataloging, just that we don't have a version to use, thus no package to raise up
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
metadata, metadataLocations, err := newAlpineConfiguration(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var locations []file.Location
|
||||
locations = append(locations, versionLocations...)
|
||||
locations = append(locations, metadataLocations...)
|
||||
|
||||
p := newPackage(version, *metadata, locations...)
|
||||
|
||||
return []pkg.Package{p}, nil, nil
|
||||
}
|
||||
|
||||
func newPackage(version string, metadata AlpineConfiguration, locations ...file.Location) pkg.Package {
|
||||
return pkg.Package{
|
||||
Name: "alpine-configuration",
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
Type: pkg.Type("system-configuration"), // you can make up your own package type here or use an existing one
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func newAlpineConfiguration(resolver file.Resolver) (*AlpineConfiguration, []file.Location, error) {
|
||||
var locations []file.Location
|
||||
|
||||
keys, keyLocations, err := getAPKKeys(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
locations = append(locations, keyLocations...)
|
||||
|
||||
return &AlpineConfiguration{
|
||||
APKKeys: keys,
|
||||
}, locations, nil
|
||||
|
||||
}
|
||||
|
||||
func getVersion(resolver file.Resolver) (string, []file.Location, error) {
|
||||
locations, err := resolver.FilesByPath("/etc/alpine-release")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to get alpine version: %w", err)
|
||||
}
|
||||
if len(locations) == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(locations[0])
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
|
||||
}
|
||||
|
||||
version, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
|
||||
}
|
||||
|
||||
return string(version), locations, nil
|
||||
}
|
||||
|
||||
func getAPKKeys(resolver file.Resolver) (map[string]string, []file.Location, error) {
|
||||
// name-to-content values
|
||||
keyContent := make(map[string]string)
|
||||
|
||||
locations, err := resolver.FilesByGlob("/etc/apk/keys/*.rsa.pub")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get apk keys: %w", err)
|
||||
}
|
||||
for _, location := range locations {
|
||||
basename := path.Base(location.RealPath)
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
|
||||
}
|
||||
keyContent[basename] = string(content)
|
||||
}
|
||||
return keyContent, locations, nil
|
||||
}
|
||||
|
||||
type AlpineConfiguration struct {
|
||||
APKKeys map[string]string `json:"apkKeys" yaml:"apkKeys"`
|
||||
// Add more data you want to capture as part of the package metadata here...
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// automagically get a source.Source for arbitrary string input
|
||||
src := getSource(imageReference())
|
||||
|
||||
// will catalog the given source and return a SBOM keeping in mind several configurable options
|
||||
sbom := getSBOM(src)
|
||||
|
||||
// show a simple package summary
|
||||
summarize(sbom)
|
||||
|
||||
// show the alpine-configuration cataloger results
|
||||
showAlpineConfiguration(sbom)
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
fmt.Println("detecting source type for input:", input, "...")
|
||||
|
||||
detection, err := source.Detect(input,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func getSBOM(src source.Source) sbom.SBOM {
|
||||
fmt.Println("creating SBOM...")
|
||||
|
||||
cfg := syft.DefaultCreateSBOMConfig().
|
||||
// run the catalogers in parallel (5 at a time concurrently max)
|
||||
WithParallelism(5).
|
||||
// bake a specific tool name and version into the SBOM
|
||||
WithTool("my-tool", "v1.0").
|
||||
// catalog all files with 3 digests
|
||||
WithFilesConfig(
|
||||
filecataloging.DefaultConfig().
|
||||
WithSelection(file.AllFilesSelection).
|
||||
WithHashers(
|
||||
crypto.MD5,
|
||||
crypto.SHA1,
|
||||
crypto.SHA256,
|
||||
),
|
||||
).
|
||||
// only use OS related catalogers that would have been used with the kind of
|
||||
// source type (container image or directory), but also add a specific python cataloger
|
||||
WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
WithSubSelections("os").
|
||||
WithAdditions("python-package-cataloger"),
|
||||
).
|
||||
// which relationships to include
|
||||
WithRelationshipsConfig(
|
||||
cataloging.RelationshipsConfig{
|
||||
PackageFileOwnership: true,
|
||||
PackageFileOwnershipOverlap: true,
|
||||
ExcludeBinaryPackagesWithFileOwnershipOverlap: true,
|
||||
},
|
||||
).
|
||||
// add your own cataloger to the mix
|
||||
WithCatalogers(
|
||||
pkgcataloging.NewAlwaysEnabledCatalogerReference(
|
||||
newAlpineConfigurationCataloger(),
|
||||
),
|
||||
)
|
||||
|
||||
s, err := syft.CreateSBOM(context.Background(), src, cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return *s
|
||||
}
|
||||
|
||||
func summarize(s sbom.SBOM) {
|
||||
fmt.Printf("Cataloged %d packages:\n", s.Artifacts.Packages.PackageCount())
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
fmt.Printf(" - %s@%s (%s)\n", p.Name, p.Version, p.Type)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showAlpineConfiguration(s sbom.SBOM) {
|
||||
pkgs := s.Artifacts.Packages.PackagesByName("alpine-configuration")
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Println("no alpine-configuration package found")
|
||||
return
|
||||
}
|
||||
|
||||
p := pkgs[0]
|
||||
|
||||
fmt.Printf("All 'alpine-configuration' packages: %s\n", p.Version)
|
||||
meta, err := yaml.Marshal(p.Metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(meta))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
// automagically get a source.Source for arbitrary string input
|
||||
src := getSource(imageReference())
|
||||
|
||||
// catalog the given source and return a SBOM
|
||||
sbom := getSBOM(src)
|
||||
|
||||
// take the SBOM object and encode it into the syft-json representation
|
||||
bytes := formatSBOM(sbom)
|
||||
|
||||
// show the SBOM!
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
detection, err := source.Detect(input,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func getSBOM(src source.Source) sbom.SBOM {
|
||||
s, err := syft.CreateSBOM(context.Background(), src, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return *s
|
||||
}
|
||||
|
||||
func formatSBOM(s sbom.SBOM) []byte {
|
||||
bytes, err := format.Encode(s, syftjson.NewFormatEncoder())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/format"
|
||||
)
|
||||
|
||||
//go:embed alpine.syft.json
|
||||
var sbomContents string
|
||||
|
||||
func main() {
|
||||
// decode the SBOM
|
||||
fmt.Println("decoding SBOM...")
|
||||
sbom, sbomFormat, formatVersion, err := format.Decode(sbomReader())
|
||||
if err != nil {
|
||||
fmt.Printf("failed to decode sbom: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("SBOM format: %s@%s\n", sbomFormat, formatVersion)
|
||||
|
||||
// print packages found...
|
||||
fmt.Println("\nPackages found:")
|
||||
for _, pkg := range sbom.Artifacts.Packages.Sorted() {
|
||||
fmt.Printf(" %s : %s@%s (%s)\n", pkg.ID(), pkg.Name, pkg.Version, pkg.Type)
|
||||
}
|
||||
|
||||
// print files found...
|
||||
fmt.Println("\nFiles found:")
|
||||
for c, f := range sbom.Artifacts.FileMetadata {
|
||||
fmt.Printf(" %s : %s\n", c.ID(), f.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func sbomReader() io.Reader {
|
||||
// read file from sys args (or use the default)
|
||||
var reader io.Reader
|
||||
if len(os.Args) < 2 {
|
||||
reader = strings.NewReader(sbomContents)
|
||||
} else {
|
||||
var err error
|
||||
reader, err = os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return reader
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
/*
|
||||
This example demonstrates how to create a source object from a generic string input.
|
||||
|
||||
Example inputs:
|
||||
alpine:3.19 pull an image from the docker daemon, podman, or the registry (based on what's available)
|
||||
./my.tar interpret a local archive as an OCI archive, docker save archive, or raw file from disk to catalog
|
||||
docker:yourrepo/yourimage:tag explicitly use the Docker daemon
|
||||
podman:yourrepo/yourimage:tag explicitly use the Podman daemon
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
file:path/to/yourproject/file read directly from a path on disk (any single file)
|
||||
|
||||
*/
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
detection, err := source.Detect(
|
||||
imageReference(),
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Show a basic description of the source to the screen
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(src.Describe()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
/*
|
||||
This shows how to create a source from an image reference. This is useful when you are programmatically always
|
||||
expecting to catalog a container image and always from the same source (e.g. docker daemon, podman, registry, etc).
|
||||
*/
|
||||
|
||||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
platform, err := image.NewPlatform("linux/amd64")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := source.NewFromStereoscopeImage(
|
||||
source.StereoscopeImageConfig{
|
||||
Reference: imageReference(),
|
||||
From: image.OciRegistrySource, // always use the registry, there are several other "Source" options here
|
||||
Platform: platform,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Show a basic description of the source to the screen
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(src.Describe()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func imageReference() string {
|
||||
// read an image string reference from the command line or use a default
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return defaultImage
|
||||
}
|
||||
@@ -9,30 +9,30 @@ require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
|
||||
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/anchore/stereoscope v0.0.0-20240118133533-eb656fc71793
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240202153536-bfa15e446f06
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/charmbracelet/bubbles v0.17.1
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/docker/docker v25.0.1+incompatible
|
||||
github.com/docker/docker v25.0.3+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.2.0
|
||||
github.com/gkampitakis/go-snaps v0.5.0
|
||||
github.com/gkampitakis/go-snaps v0.5.2
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-test/deep v1.1.0
|
||||
@@ -74,7 +74,7 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/mod v0.15.0
|
||||
golang.org/x/net v0.20.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.28.0
|
||||
@@ -169,7 +169,7 @@ require (
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/runc v1.1.12 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
@@ -180,7 +180,7 @@ require (
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.6 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
@@ -244,3 +244,5 @@ retract (
|
||||
v0.53.2
|
||||
v0.53.1 // Published accidentally with incorrect license in depdencies
|
||||
)
|
||||
|
||||
replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2
|
||||
|
||||
@@ -89,10 +89,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
||||
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU=
|
||||
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448 h1:ZgecmkxhH5im+9jPs7Ra1Thmv/p4IBDsoCFD6W8pENg=
|
||||
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448/go.mod h1:t5Mld8naKcG8RTPjW/2n7bfyBKFl1A6PvtXw+v64gK0=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
@@ -105,10 +107,10 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20240118133533-eb656fc71793 h1:wji+qdjsV7ooolBwb3faVZnEK3WtY/kcT5473kxVZS4=
|
||||
github.com/anchore/stereoscope v0.0.0-20240118133533-eb656fc71793/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426 h1:agoiZchSf1Nnnos1azwIg5hk5Ao9TzZNBD9++AChGEg=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240202153536-bfa15e446f06 h1:3NAS33Bqrw87wckTff6/yXYDL1h3Wm6OiqaR1kcvW10=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240202153536-bfa15e446f06/go.mod h1:uydT2ful8TY7Hr1WH1V1ZecSq/2TqXpAsGkMiy7lxD0=
|
||||
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=
|
||||
@@ -149,19 +151,17 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
|
||||
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -180,7 +180,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
|
||||
@@ -204,7 +203,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE=
|
||||
@@ -223,15 +221,14 @@ github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qe
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.1+incompatible h1:k5TYd5rIVQRSqcTwCID+cyVA0yRg86+Pcrz1ls0/frA=
|
||||
github.com/docker/docker v25.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
|
||||
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
@@ -268,7 +265,6 @@ github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
@@ -283,8 +279,8 @@ github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvp
|
||||
github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.0 h1:Qpk1+V1WJszsDvYMgrI15XbUvWqpsFvFxgaXKV6RU9Q=
|
||||
github.com/gkampitakis/go-snaps v0.5.0/go.mod h1:gX47VYsnvqHsjBrTrpaXfm1Dm1Xg+nREZPKRT8xifV8=
|
||||
github.com/gkampitakis/go-snaps v0.5.2 h1:ay/6f7WHwRkOgpBec9DjMLRBAApziJommZ21NkOOCwY=
|
||||
github.com/gkampitakis/go-snaps v0.5.2/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
@@ -316,7 +312,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -505,7 +500,6 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -551,8 +545,6 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 h1:tQRHcLQwnwrPq2j2Qra/NnyjyESBGwdeBeVdAE9kXYg=
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@@ -577,7 +569,6 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
@@ -593,7 +584,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
@@ -613,12 +603,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -667,8 +655,9 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
@@ -697,7 +686,6 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
@@ -762,7 +750,6 @@ github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM=
|
||||
github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y=
|
||||
github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ=
|
||||
github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
@@ -782,7 +769,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w=
|
||||
@@ -791,8 +777,6 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
|
||||
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8=
|
||||
@@ -915,8 +899,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -951,7 +935,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
@@ -1012,7 +995,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1020,7 +1002,6 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1062,12 +1043,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "14.0.0"
|
||||
JSONSchemaVersion = "16.0.3"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filedigest"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filemetadata"
|
||||
@@ -100,6 +101,31 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
return NewTask("file-content-cataloger", fn)
|
||||
}
|
||||
|
||||
func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config) Task {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
}
|
||||
|
||||
cat := executable.NewCataloger(cfg)
|
||||
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.Executables = result
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn)
|
||||
}
|
||||
|
||||
// TODO: this should be replaced with a fix that allows passing a coordinate or location iterator to the cataloger
|
||||
// Today internal to both cataloger this functions differently: a slice of coordinates vs a channel of locations
|
||||
func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accessor) ([]file.Coordinates, bool) {
|
||||
|
||||
@@ -113,7 +113,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
// we might have binary classified CPE already with the package so we want to append here
|
||||
dictionaryCPE, ok := cpe.DictionaryFind(p)
|
||||
if ok {
|
||||
log.Tracef("used CPE dictionary to find CPE for %s package %q: %s", p.Type, p.Name, dictionaryCPE.BindToFmtString())
|
||||
log.Tracef("used CPE dictionary to find CPE for %s package %q: %s", p.Type, p.Name, dictionaryCPE.Attributes.BindToFmtString())
|
||||
p.CPEs = append(p.CPEs, dictionaryCPE)
|
||||
} else {
|
||||
p.CPEs = append(p.CPEs, cpe.Generate(p)...)
|
||||
|
||||
@@ -57,6 +57,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
|
||||
newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"),
|
||||
newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"),
|
||||
newSimplePackageTaskFactory(haskell.NewHackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "haskell", "hackage", "cabal"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,15 @@ import (
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Selection file.Selection `yaml:"selection" json:"selection" mapstructure:"selection"`
|
||||
Hashers []crypto.Hash `yaml:"hashers" json:"hashers" mapstructure:"hashers"`
|
||||
Content filecontent.Config `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Selection file.Selection `yaml:"selection" json:"selection" mapstructure:"selection"`
|
||||
Hashers []crypto.Hash `yaml:"hashers" json:"hashers" mapstructure:"hashers"`
|
||||
Content filecontent.Config `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Executable executable.Config `yaml:"executable" json:"executable" mapstructure:"executable"`
|
||||
}
|
||||
|
||||
type configMarshaledForm struct {
|
||||
@@ -30,9 +32,10 @@ func DefaultConfig() Config {
|
||||
log.WithFields("error", err).Warn("unable to create file hashers")
|
||||
}
|
||||
return Config{
|
||||
Selection: file.FilesOwnedByPackageSelection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.DefaultConfig(),
|
||||
Selection: file.FilesOwnedByPackageSelection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.DefaultConfig(),
|
||||
Executable: executable.DefaultConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +75,12 @@ func (cfg Config) WithSelection(selection file.Selection) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg Config) WithHashers(hashers []crypto.Hash) Config {
|
||||
func (cfg Config) WithHashers(hashers ...crypto.Hash) Config {
|
||||
cfg.Hashers = intFile.NormalizeHashes(hashers)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg Config) WithContentConfig(content filecontent.Config) Config {
|
||||
cfg.Content = content
|
||||
return cfg
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cpe
|
||||
|
||||
import "sort"
|
||||
|
||||
type BySourceThenSpecificity []CPE
|
||||
|
||||
func (b BySourceThenSpecificity) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b BySourceThenSpecificity) Less(i, j int) bool {
|
||||
sourceOrder := map[Source]int{
|
||||
NVDDictionaryLookupSource: 1,
|
||||
DeclaredSource: 2,
|
||||
GeneratedSource: 3,
|
||||
}
|
||||
|
||||
getRank := func(source Source) int {
|
||||
if rank, exists := sourceOrder[source]; exists {
|
||||
return rank
|
||||
}
|
||||
return 4 // Sourced we don't know about can't be assigned special priority, so
|
||||
// are considered ties.
|
||||
}
|
||||
iSource := b[i].Source
|
||||
jSource := b[j].Source
|
||||
rankI, rankJ := getRank(iSource), getRank(jSource)
|
||||
if rankI != rankJ {
|
||||
return rankI < rankJ
|
||||
}
|
||||
|
||||
return isMoreSpecific(b[i].Attributes, b[j].Attributes)
|
||||
}
|
||||
|
||||
func (b BySourceThenSpecificity) Swap(i, j int) {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
|
||||
var _ sort.Interface = (*BySourceThenSpecificity)(nil)
|
||||
@@ -0,0 +1,74 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBySourceThenSpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
want []CPE
|
||||
}{
|
||||
{
|
||||
name: "empty case",
|
||||
},
|
||||
{
|
||||
name: "nvd before generated",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "declared before generated",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "most specific attributes of equal sources",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "most specific attributes of unknown sources",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", ""),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", "some-other-unknown-source"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", "some-unknown-source"),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*", "some-other-unknown-source"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", ""),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", "some-unknown-source"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sort.Sort(BySourceThenSpecificity(tt.input))
|
||||
assert.Equal(t, tt.want, tt.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
+23
-16
@@ -6,15 +6,22 @@ import (
|
||||
|
||||
var _ sort.Interface = (*BySpecificity)(nil)
|
||||
|
||||
type BySpecificity []CPE
|
||||
type BySpecificity []Attributes
|
||||
|
||||
func (c BySpecificity) Len() int { return len(c) }
|
||||
|
||||
func (c BySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
func (c BySpecificity) Less(i, j int) bool {
|
||||
iScore := weightedCountForSpecifiedFields(c[i])
|
||||
jScore := weightedCountForSpecifiedFields(c[j])
|
||||
return isMoreSpecific(c[i], c[j])
|
||||
}
|
||||
|
||||
// Returns true if i is more specific than j, with some
|
||||
// tie breaking mechanisms to make sorting equally-specific cpe Attributes
|
||||
// deterministic.
|
||||
func isMoreSpecific(i, j Attributes) bool {
|
||||
iScore := weightedCountForSpecifiedFields(i)
|
||||
jScore := weightedCountForSpecifiedFields(j)
|
||||
|
||||
// check weighted sort first
|
||||
if iScore != jScore {
|
||||
@@ -22,28 +29,28 @@ func (c BySpecificity) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
// sort longer fields to top
|
||||
if countFieldLength(c[i]) != countFieldLength(c[j]) {
|
||||
return countFieldLength(c[i]) > countFieldLength(c[j])
|
||||
if countFieldLength(i) != countFieldLength(j) {
|
||||
return countFieldLength(i) > countFieldLength(j)
|
||||
}
|
||||
|
||||
// if score and length are equal then text sort
|
||||
// note that we are not using String from the syft pkg
|
||||
// as we are not encoding/decoding this CPE string so we don't
|
||||
// need the proper quoted version of the CPE.
|
||||
return c[i].BindToFmtString() < c[j].BindToFmtString()
|
||||
// as we are not encoding/decoding this Attributes string so we don't
|
||||
// need the proper quoted version of the Attributes.
|
||||
return i.BindToFmtString() < j.BindToFmtString()
|
||||
}
|
||||
|
||||
func countFieldLength(cpe CPE) int {
|
||||
func countFieldLength(cpe Attributes) int {
|
||||
return len(cpe.Part + cpe.Vendor + cpe.Product + cpe.Version + cpe.TargetSW)
|
||||
}
|
||||
|
||||
func weightedCountForSpecifiedFields(cpe CPE) int {
|
||||
checksForSpecifiedField := []func(cpe CPE) (bool, int){
|
||||
func(cpe CPE) (bool, int) { return cpe.Part != "", 2 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Vendor != "", 3 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Product != "", 4 },
|
||||
func(cpe CPE) (bool, int) { return cpe.Version != "", 1 },
|
||||
func(cpe CPE) (bool, int) { return cpe.TargetSW != "", 1 },
|
||||
func weightedCountForSpecifiedFields(cpe Attributes) int {
|
||||
checksForSpecifiedField := []func(cpe Attributes) (bool, int){
|
||||
func(cpe Attributes) (bool, int) { return cpe.Part != "", 2 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Vendor != "", 3 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Product != "", 4 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.Version != "", 1 },
|
||||
func(cpe Attributes) (bool, int) { return cpe.TargetSW != "", 1 },
|
||||
}
|
||||
|
||||
weightedCount := 0
|
||||
|
||||
@@ -10,81 +10,81 @@ import (
|
||||
func Test_BySpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
expected []CPE
|
||||
input []Attributes
|
||||
expected []Attributes
|
||||
}{
|
||||
{
|
||||
name: "sort strictly by wfn *",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort strictly by field length",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length and specificity",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
MustAttributes("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
MustAttributes("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length, specificity, dash",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
input: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
expected: []Attributes{
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
MustAttributes("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+69
-30
@@ -8,9 +8,29 @@ import (
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
)
|
||||
|
||||
// CPE contains the attributes of an NVD Attributes and a string
|
||||
// describing where Syft got the Attributes, e.g. generated by heuristics
|
||||
// vs looked up in the NVD Attributes dictionary
|
||||
type CPE struct {
|
||||
Attributes Attributes
|
||||
Source Source
|
||||
}
|
||||
|
||||
type Source string
|
||||
|
||||
func (c Source) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
const (
|
||||
GeneratedSource Source = "syft-generated"
|
||||
NVDDictionaryLookupSource Source = "nvd-cpe-dictionary"
|
||||
DeclaredSource Source = "declared"
|
||||
)
|
||||
|
||||
const Any = ""
|
||||
|
||||
type CPE struct {
|
||||
type Attributes struct {
|
||||
Part string
|
||||
Vendor string
|
||||
Product string
|
||||
@@ -24,19 +44,19 @@ type CPE struct {
|
||||
Language string
|
||||
}
|
||||
|
||||
func (c CPE) asAttributes() wfn.Attributes {
|
||||
func (c Attributes) asAttributes() wfn.Attributes {
|
||||
return wfn.Attributes(c)
|
||||
}
|
||||
|
||||
func fromAttributes(a wfn.Attributes) CPE {
|
||||
return CPE(a)
|
||||
func fromAttributes(a wfn.Attributes) Attributes {
|
||||
return Attributes(a)
|
||||
}
|
||||
|
||||
func (c CPE) BindToFmtString() string {
|
||||
func (c Attributes) BindToFmtString() string {
|
||||
return c.asAttributes().BindToFmtString()
|
||||
}
|
||||
|
||||
func NewWithAny() CPE {
|
||||
func NewWithAny() Attributes {
|
||||
return fromAttributes(*(wfn.NewAttributesWithAny()))
|
||||
}
|
||||
|
||||
@@ -46,36 +66,55 @@ const (
|
||||
|
||||
// This regex string is taken from
|
||||
// https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd which has the official cpe spec
|
||||
// This first part matches CPE urls and the second part matches binding strings
|
||||
// This first part matches Attributes urls and the second part matches binding strings
|
||||
const cpeRegexString = ((`^([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\._\-~%]*){0,6})`) +
|
||||
// Or match the CPE binding string
|
||||
// Or match the Attributes binding string
|
||||
// Note that we had to replace '`' with '\x60' to escape the backticks
|
||||
`|(cpe:2\.3:[aho\*\-](:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&'\(\)\+,/:;<=>@\[\]\^\x60\{\|}~]))+(\?*|\*?))|[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&'\(\)\+,/:;<=>@\[\]\^\x60\{\|}~]))+(\?*|\*?))|[\*\-])){4})$`)
|
||||
|
||||
var cpeRegex = regexp.MustCompile(cpeRegexString)
|
||||
|
||||
// New will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
|
||||
// characters is allowed, however, a more strict validation is done after this sanitization process.
|
||||
func New(cpeStr string) (CPE, error) {
|
||||
// get a CPE object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
|
||||
c, err := newWithoutValidation(cpeStr)
|
||||
func New(value string, source Source) (CPE, error) {
|
||||
attributes, err := NewAttributes(value)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("unable to parse CPE string: %w", err)
|
||||
}
|
||||
|
||||
// ensure that this CPE can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
return CPE{}, err
|
||||
}
|
||||
return CPE{
|
||||
Attributes: attributes,
|
||||
Source: source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// we don't return the sanitized string, as this is a concern for later when creating CPE strings. In fact, since
|
||||
// NewAttributes will parse a formatted Attributes string and return a Attributes object. Some input, such as the existence of whitespace
|
||||
// characters is allowed, however, a more strict validation is done after this sanitization process.
|
||||
func NewAttributes(cpeStr string) (Attributes, error) {
|
||||
// get a Attributes object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
|
||||
c, err := newWithoutValidation(cpeStr)
|
||||
if err != nil {
|
||||
return Attributes{}, fmt.Errorf("unable to parse Attributes string: %w", err)
|
||||
}
|
||||
|
||||
// ensure that this Attributes can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
return Attributes{}, err
|
||||
}
|
||||
|
||||
// we don't return the sanitized string, as this is a concern for later when creating Attributes strings. In fact, since
|
||||
// sanitization is lossy (whitespace is replaced, not escaped) it's important that the raw values are left as.
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Must returns a CPE or panics if the provided string is not valid
|
||||
func Must(cpeStr string) CPE {
|
||||
c, err := New(cpeStr)
|
||||
func Must(cpeStr string, source Source) CPE {
|
||||
c := MustAttributes(cpeStr)
|
||||
return CPE{
|
||||
Attributes: c,
|
||||
Source: source,
|
||||
}
|
||||
}
|
||||
|
||||
func MustAttributes(cpeStr string) Attributes {
|
||||
c, err := NewAttributes(cpeStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -83,22 +122,22 @@ func Must(cpeStr string) CPE {
|
||||
}
|
||||
|
||||
func ValidateString(cpeStr string) error {
|
||||
// We should filter out all CPEs that do not match the official CPE regex
|
||||
// The facebook nvdtools parser can sometimes incorrectly parse invalid CPE strings
|
||||
// We should filter out all CPEs that do not match the official Attributes regex
|
||||
// The facebook nvdtools parser can sometimes incorrectly parse invalid Attributes strings
|
||||
if !cpeRegex.MatchString(cpeStr) {
|
||||
return fmt.Errorf("failed to parse CPE=%q as it doesn't match the regex=%s", cpeStr, cpeRegexString)
|
||||
return fmt.Errorf("failed to parse Attributes=%q as it doesn't match the regex=%s", cpeStr, cpeRegexString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newWithoutValidation(cpeStr string) (CPE, error) {
|
||||
func newWithoutValidation(cpeStr string) (Attributes, error) {
|
||||
value, err := wfn.Parse(cpeStr)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("failed to parse CPE=%q: %w", cpeStr, err)
|
||||
return Attributes{}, fmt.Errorf("failed to parse Attributes=%q: %w", cpeStr, err)
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return CPE{}, fmt.Errorf("failed to parse CPE=%q", cpeStr)
|
||||
return Attributes{}, fmt.Errorf("failed to parse Attributes=%q", cpeStr)
|
||||
}
|
||||
|
||||
syftCPE := fromAttributes(*value)
|
||||
@@ -120,7 +159,7 @@ func newWithoutValidation(cpeStr string) (CPE, error) {
|
||||
}
|
||||
|
||||
func normalizeField(field string) string {
|
||||
// replace spaces with underscores (per section 5.3.2 of the CPE spec v 2.3)
|
||||
// replace spaces with underscores (per section 5.3.2 of the Attributes spec v 2.3)
|
||||
field = strings.ReplaceAll(field, " ", "_")
|
||||
|
||||
// keep dashes and forward slashes unescaped
|
||||
@@ -144,8 +183,8 @@ func stripSlashes(s string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c CPE) String() string {
|
||||
output := CPE{}
|
||||
func (c Attributes) String() string {
|
||||
output := Attributes{}
|
||||
output.Vendor = sanitize(c.Vendor)
|
||||
output.Product = sanitize(c.Product)
|
||||
output.Language = sanitize(c.Language)
|
||||
|
||||
+26
-26
@@ -12,38 +12,38 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
func Test_NewAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected CPE
|
||||
expected Attributes
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
input: `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
|
||||
expected: Must(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
|
||||
},
|
||||
{
|
||||
name: "dashes",
|
||||
input: `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
|
||||
expected: Must(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
|
||||
},
|
||||
{
|
||||
name: "URL escape characters",
|
||||
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
|
||||
expected: Must(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
expected: MustAttributes(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := New(test.input)
|
||||
actual, err := NewAttributes(test.input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error while creating CPE: %+v", err)
|
||||
t.Fatalf("got an error while creating Attributes: %+v", err)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(actual, test.expected); d != "" {
|
||||
t.Errorf("CPE mismatch (-want +got):\n%s", d)
|
||||
t.Errorf("Attributes mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
})
|
||||
@@ -82,9 +82,9 @@ func Test_normalizeCpeField(t *testing.T) {
|
||||
|
||||
func Test_CPEParser(t *testing.T) {
|
||||
var testCases []struct {
|
||||
CPEString string `json:"cpe-string"`
|
||||
CPEUrl string `json:"cpe-url"`
|
||||
WFN CPE `json:"wfn"`
|
||||
CPEString string `json:"cpe-string"`
|
||||
CPEUrl string `json:"cpe-url"`
|
||||
WFN Attributes `json:"wfn"`
|
||||
}
|
||||
out, err := os.ReadFile("test-fixtures/cpe-data.json")
|
||||
require.NoError(t, err)
|
||||
@@ -92,9 +92,9 @@ func Test_CPEParser(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.CPEString, func(t *testing.T) {
|
||||
c1, err := New(test.CPEString)
|
||||
c1, err := NewAttributes(test.CPEString)
|
||||
assert.NoError(t, err)
|
||||
c2, err := New(test.CPEUrl)
|
||||
c2, err := NewAttributes(test.CPEUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c1, c2)
|
||||
assert.Equal(t, c1, test.WFN)
|
||||
@@ -161,11 +161,11 @@ func Test_InvalidCPE(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c, err := New(test.in)
|
||||
c, err := NewAttributes(test.in)
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
if t.Failed() {
|
||||
t.Logf("got CPE: %q details: %+v", c, c)
|
||||
t.Logf("got Attributes: %q details: %+v", c, c)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,12 +179,12 @@ func Test_RoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cpe string
|
||||
parsedCPE CPE
|
||||
parsedCPE Attributes
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
cpe: "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "some-vendor",
|
||||
Product: "name",
|
||||
@@ -194,7 +194,7 @@ func Test_RoundTrip(t *testing.T) {
|
||||
{
|
||||
name: "escaped colon",
|
||||
cpe: "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "some-vendor",
|
||||
Product: "name",
|
||||
@@ -204,7 +204,7 @@ func Test_RoundTrip(t *testing.T) {
|
||||
{
|
||||
name: "escaped forward slash",
|
||||
cpe: "cpe:2.3:a:test\\/some-vendor:name:3.2:*:*:*:*:*:*:*",
|
||||
parsedCPE: CPE{
|
||||
parsedCPE: Attributes{
|
||||
Part: "a",
|
||||
Vendor: "test/some-vendor",
|
||||
Product: "name",
|
||||
@@ -215,13 +215,13 @@ func Test_RoundTrip(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// CPE string must be preserved through a round trip
|
||||
assert.Equal(t, test.cpe, Must(test.cpe).String())
|
||||
// The parsed CPE must be the same after a round trip
|
||||
assert.Equal(t, Must(test.cpe), Must(Must(test.cpe).String()))
|
||||
// The test case parsed CPE must be the same after parsing the input string
|
||||
assert.Equal(t, test.parsedCPE, Must(test.cpe))
|
||||
// The test case parsed CPE must produce the same string as the input cpe
|
||||
// Attributes string must be preserved through a round trip
|
||||
assert.Equal(t, test.cpe, MustAttributes(test.cpe).String())
|
||||
// The parsed Attributes must be the same after a round trip
|
||||
assert.Equal(t, MustAttributes(test.cpe), MustAttributes(MustAttributes(test.cpe).String()))
|
||||
// The test case parsed Attributes must be the same after parsing the input string
|
||||
assert.Equal(t, test.parsedCPE, MustAttributes(test.cpe))
|
||||
// The test case parsed Attributes must produce the same string as the input cpe
|
||||
assert.Equal(t, test.parsedCPE.String(), test.cpe)
|
||||
})
|
||||
}
|
||||
|
||||
+17
-15
@@ -1,25 +1,27 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func Merge(a, b []CPE) (result []CPE) {
|
||||
aCPEs := make(map[string]CPE)
|
||||
|
||||
// keep all CPEs from a and create a quick string-based lookup
|
||||
for _, aCPE := range a {
|
||||
aCPEs[aCPE.BindToFmtString()] = aCPE
|
||||
result = append(result, aCPE)
|
||||
// Merge returns unique SourcedCPEs that are found in A or B
|
||||
// Two SourcedCPEs are identical if their source and normalized string are identical
|
||||
func Merge(a, b []CPE) []CPE {
|
||||
var result []CPE
|
||||
dedupe := make(map[string]CPE)
|
||||
key := func(scpe CPE) string {
|
||||
return fmt.Sprintf("%s:%s", scpe.Source.String(), scpe.Attributes.BindToFmtString())
|
||||
}
|
||||
|
||||
// keep all unique CPEs from b
|
||||
for _, bCPE := range b {
|
||||
if _, exists := aCPEs[bCPE.BindToFmtString()]; !exists {
|
||||
result = append(result, bCPE)
|
||||
}
|
||||
for _, s := range a {
|
||||
dedupe[key(s)] = s
|
||||
}
|
||||
|
||||
sort.Sort(BySpecificity(result))
|
||||
for _, s := range b {
|
||||
dedupe[key(s)] = s
|
||||
}
|
||||
for _, val := range dedupe {
|
||||
result = append(result, val)
|
||||
}
|
||||
sort.Sort(BySourceThenSpecificity(result))
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -16,18 +16,20 @@ func Test_Merge(t *testing.T) {
|
||||
name: "merge, removing duplicates and ordered",
|
||||
input: [][]CPE{
|
||||
{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", NVDDictionaryLookupSource),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*", DeclaredSource),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", GeneratedSource),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -185,6 +185,9 @@ func (c *CreateSBOMConfig) fileTasks() []task.Task {
|
||||
if t := task.NewFileContentCatalogerTask(c.Files.Content); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
if t := task.NewExecutableCatalogerTask(c.Files.Selection, c.Files.Executable); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
|
||||
return tsks
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -106,7 +106,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -125,7 +125,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -139,11 +139,11 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
{
|
||||
name: "no file digest cataloger",
|
||||
src: imgSrc,
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers(nil)),
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers()),
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(false, true), // note: the digest cataloger is not included
|
||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -181,7 +181,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
// note: there is a single group of catalogers for pkgs and files
|
||||
append(
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true)...,
|
||||
fileCatalogerNames(true, true, true)...,
|
||||
),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
@@ -202,7 +202,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -222,7 +222,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -242,7 +242,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -263,7 +263,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -283,7 +283,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
@@ -367,11 +367,14 @@ func pkgCatalogerNamesWithTagOrName(t *testing.T, token string) []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func fileCatalogerNames(digest, metadata bool) []string {
|
||||
func fileCatalogerNames(digest, metadata, executable bool) []string {
|
||||
var names []string
|
||||
if digest {
|
||||
names = append(names, "file-digest-cataloger")
|
||||
}
|
||||
if executable {
|
||||
names = append(names, "file-executable-cataloger")
|
||||
}
|
||||
if metadata {
|
||||
names = append(names, "file-metadata-cataloger")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
MIMETypes []string `json:"mime-types" yaml:"mime-types" mapstructure:"mime-types"`
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type Cataloger struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
m := mimetype.ExecutableMIMETypeSet.List()
|
||||
sort.Strings(m)
|
||||
return Config{
|
||||
MIMETypes: m,
|
||||
Globs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCataloger(cfg Config) *Cataloger {
|
||||
return &Cataloger{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
||||
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
||||
}
|
||||
|
||||
locs, err = filterByGlobs(locs, i.config.Globs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prog := catalogingProgress(int64(len(locs)))
|
||||
|
||||
results := make(map[file.Coordinates]file.Executable)
|
||||
for _, loc := range locs {
|
||||
prog.AtomicStage.Set(loc.Path())
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||
continue
|
||||
}
|
||||
exec, err := processExecutable(loc, reader.(unionreader.UnionReader))
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
|
||||
}
|
||||
if exec != nil {
|
||||
prog.Increment()
|
||||
results[loc.Coordinates] = *exec
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("executable cataloger processed %d files", len(results))
|
||||
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
info := monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
Default: "Executables",
|
||||
},
|
||||
ParentID: monitor.TopLevelCatalogingTaskID,
|
||||
}
|
||||
|
||||
return bus.StartCatalogerTask(info, locations, "")
|
||||
}
|
||||
|
||||
func filterByGlobs(locs []file.Location, globs []string) ([]file.Location, error) {
|
||||
if len(globs) == 0 {
|
||||
return locs, nil
|
||||
}
|
||||
var filteredLocs []file.Location
|
||||
for _, loc := range locs {
|
||||
matches, err := locationMatchesGlob(loc, globs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if matches {
|
||||
filteredLocs = append(filteredLocs, loc)
|
||||
}
|
||||
}
|
||||
return filteredLocs, nil
|
||||
}
|
||||
|
||||
func locationMatchesGlob(loc file.Location, globs []string) (bool, error) {
|
||||
for _, glob := range globs {
|
||||
for _, path := range []string{loc.RealPath, loc.AccessPath} {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
matches, err := doublestar.Match(glob, path)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to match glob %q to path %q: %w", glob, path, err)
|
||||
}
|
||||
if matches {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
securityFeatures, err := findSecurityFeatures(format, reader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to determine security features for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.SecurityFeatures = securityFeatures
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
// read the first sector of the file
|
||||
buf := make([]byte, 512)
|
||||
n, err := reader.ReadAt(buf, 0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read first sector of file: %w", err)
|
||||
}
|
||||
if n < 512 {
|
||||
return "", fmt.Errorf("unable to read enough bytes to determine executable format")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isMacho(buf):
|
||||
return file.MachO, nil
|
||||
case isPE(buf):
|
||||
return file.PE, nil
|
||||
case isELF(buf):
|
||||
return file.ELF, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func isMacho(by []byte) bool {
|
||||
// sourced from https://github.com/gabriel-vasile/mimetype/blob/02af149c0dfd1444d9256fc33c2012bb3153e1d2/internal/magic/binary.go#L44
|
||||
|
||||
if classOrMachOFat(by) && by[7] < 20 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(by) < 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
be := binary.BigEndian.Uint32(by)
|
||||
le := binary.LittleEndian.Uint32(by)
|
||||
|
||||
return be == macho.Magic32 ||
|
||||
le == macho.Magic32 ||
|
||||
be == macho.Magic64 ||
|
||||
le == macho.Magic64
|
||||
}
|
||||
|
||||
// Java bytecode and Mach-O binaries share the same magic number.
|
||||
// More info here https://github.com/threatstack/libmagic/blob/master/magic/Magdir/cafebabe
|
||||
func classOrMachOFat(in []byte) bool {
|
||||
// sourced from https://github.com/gabriel-vasile/mimetype/blob/02af149c0dfd1444d9256fc33c2012bb3153e1d2/internal/magic/binary.go#L44
|
||||
|
||||
// There should be at least 8 bytes for both of them because the only way to
|
||||
// quickly distinguish them is by comparing byte at position 7
|
||||
if len(in) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.HasPrefix(in, []byte{0xCA, 0xFE, 0xBA, 0xBE})
|
||||
}
|
||||
|
||||
func isPE(by []byte) bool {
|
||||
return bytes.HasPrefix(by, []byte("MZ"))
|
||||
}
|
||||
|
||||
func isELF(by []byte) bool {
|
||||
return bytes.HasPrefix(by, []byte(elf.ELFMAG))
|
||||
}
|
||||
|
||||
func findSecurityFeatures(format file.ExecutableFormat, reader unionreader.UnionReader) (*file.ELFSecurityFeatures, error) {
|
||||
// TODO: add support for PE and MachO
|
||||
switch format { //nolint: gocritic
|
||||
case file.ELF:
|
||||
return findELFSecurityFeatures(reader) //nolint: gocritic
|
||||
// case file.PE:
|
||||
// return findPESecurityFeatures(reader)
|
||||
// case file.MachO:
|
||||
// return findMachOSecurityFeatures(reader)
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported executable format: %q", format)
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func findELFSecurityFeatures(reader unionreader.UnionReader) (*file.ELFSecurityFeatures, error) {
|
||||
f, err := elf.NewFile(reader)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
features := file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: isElfSymbolTableStripped(f),
|
||||
StackCanary: checkElfStackCanary(f),
|
||||
NoExecutable: checkElfNXProtection(f),
|
||||
RelocationReadOnly: checkElfRelROProtection(f),
|
||||
PositionIndependentExecutable: isELFPIE(f),
|
||||
DynamicSharedObject: isELFDSO(f),
|
||||
LlvmSafeStack: checkLLVMSafeStack(f),
|
||||
LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f),
|
||||
ClangFortifySource: checkClangFortifySource(f),
|
||||
}
|
||||
|
||||
return &features, nil
|
||||
}
|
||||
|
||||
func isElfSymbolTableStripped(file *elf.File) bool {
|
||||
return file.Section(".symtab") == nil
|
||||
}
|
||||
|
||||
func checkElfStackCanary(file *elf.File) *bool {
|
||||
return hasAnyDynamicSymbols(file, "__stack_chk_fail", "__stack_chk_guard")
|
||||
}
|
||||
|
||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||
dynSyms, err := file.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read dynamic symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
nameSet := strset.New(symbolNames...)
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if nameSet.Has(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func checkElfNXProtection(file *elf.File) bool {
|
||||
// find the program headers until you find the GNU_STACK segment
|
||||
for _, prog := range file.Progs {
|
||||
if prog.Type == elf.PT_GNU_STACK {
|
||||
// check if the GNU_STACK segment is executable
|
||||
return prog.Flags&elf.PF_X == 0
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkElfRelROProtection(f *elf.File) file.RelocationReadOnly {
|
||||
// background on relro https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro
|
||||
hasRelro := false
|
||||
hasBindNow := hasBindNowDynTagOrFlag(f)
|
||||
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type == elf.PT_GNU_RELRO {
|
||||
hasRelro = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case hasRelro && hasBindNow:
|
||||
return file.RelocationReadOnlyFull
|
||||
case hasRelro:
|
||||
return file.RelocationReadOnlyPartial
|
||||
default:
|
||||
return file.RelocationReadOnlyNone
|
||||
}
|
||||
}
|
||||
|
||||
func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
||||
if hasElfDynTag(f, elf.DT_BIND_NOW) {
|
||||
// support older binaries...
|
||||
return true
|
||||
}
|
||||
|
||||
// "DT_BIND_NOW ... use has been superseded by the DF_BIND_NOW flag"
|
||||
// source: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||
return hasElfDynFlag(f, elf.DF_BIND_NOW)
|
||||
}
|
||||
|
||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read DT_FLAGS from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read DT_FLAGS_1 from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynTag(f *elf.File, tag elf.DynTag) bool {
|
||||
// source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280
|
||||
|
||||
ds := f.SectionByType(elf.SHT_DYNAMIC)
|
||||
if ds == nil {
|
||||
return false
|
||||
}
|
||||
d, err := ds.Data()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for len(d) > 0 {
|
||||
var t elf.DynTag
|
||||
switch f.Class {
|
||||
case elf.ELFCLASS32:
|
||||
t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
|
||||
d = d[8:]
|
||||
case elf.ELFCLASS64:
|
||||
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
|
||||
d = d[16:]
|
||||
}
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isELFPIE(f *elf.File) bool {
|
||||
// being a shared object is not sufficient to be a PIE, the explicit flag must be set also
|
||||
return isELFDSO(f) && hasElfDynFlag1(f, elf.DF_1_PIE)
|
||||
}
|
||||
|
||||
func isELFDSO(f *elf.File) bool {
|
||||
return f.Type == elf.ET_DYN
|
||||
}
|
||||
|
||||
func checkLLVMSafeStack(file *elf.File) *bool {
|
||||
// looking for the presence of https://github.com/microsoft/compiler-rt/blob/30b3b8cb5c9a0854f2f40f187c6f6773561a35f2/lib/safestack/safestack.cc#L207
|
||||
return hasAnyDynamicSymbols(file, "__safestack_init")
|
||||
}
|
||||
|
||||
func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
||||
// look for any symbols that are functions and end with ".cfi"
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && strings.HasSuffix(sym.Name, ".cfi") {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func isFunction(sym elf.Symbol) bool {
|
||||
return elf.ST_TYPE(sym.Info) == elf.STT_FUNC
|
||||
}
|
||||
|
||||
var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
||||
|
||||
func checkClangFortifySource(file *elf.File) *bool {
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && fortifyPattern.MatchString(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func Test_findELFSecurityFeatures(t *testing.T) {
|
||||
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want *file.ELFSecurityFeatures
|
||||
wantStripped bool
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "detect canary",
|
||||
fixture: "bin/with_canary",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(true), // ! important !
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect nx",
|
||||
fixture: "bin/with_nx",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
NoExecutable: true, // ! important !
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect relro",
|
||||
fixture: "bin/with_relro",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyFull, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect partial relro",
|
||||
fixture: "bin/with_partial_relro",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect pie",
|
||||
fixture: "bin/with_pie",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyNone,
|
||||
PositionIndependentExecutable: true, // ! important !
|
||||
DynamicSharedObject: true, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect dso",
|
||||
fixture: "bin/pie_false_positive.so",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
NoExecutable: true,
|
||||
PositionIndependentExecutable: false, // ! important !
|
||||
DynamicSharedObject: true, // ! important !
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect safestack",
|
||||
fixture: "bin/with_safestack",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(true), // ! important !
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect cfi",
|
||||
fixture: "bin/with_cfi",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(true), // ! important !
|
||||
ClangFortifySource: boolRef(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "detect fortify",
|
||||
fixture: "bin/with_fortify",
|
||||
want: &file.ELFSecurityFeatures{
|
||||
NoExecutable: true,
|
||||
StackCanary: boolRef(false),
|
||||
RelocationReadOnly: file.RelocationReadOnlyPartial,
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(false),
|
||||
ClangFortifySource: boolRef(true), // ! important !
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
got, err := findELFSecurityFeatures(readerForFixture(t, tt.fixture))
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("findELFSecurityFeatures() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bin
|
||||
actual_verify
|
||||
Dockerfile.sha256
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM gcc:9.5.0
|
||||
|
||||
RUN apt update -y && apt install -y clang cmake git make m4 pkg-config zlib1g-dev
|
||||
|
||||
## from https://github.com/runsafesecurity/selfrando/blob/tb-v0.4.2/docs/linux-build-instructions.md
|
||||
#RUN git clone https://github.com/runsafesecurity/selfrando.git && \
|
||||
# export SR_ARCH=`uname -m | sed s/i686/x86/` && \
|
||||
# cd selfrando && \
|
||||
# cmake . -DSR_DEBUG_LEVEL=env -DCMAKE_BUILD_TYPE=Release -DSR_BUILD_LIBELF=1 \
|
||||
# -DSR_ARCH=$SR_ARCH -DSR_LOG=console \
|
||||
# -DSR_FORCE_INPLACE=1 -G "Unix Makefiles" \
|
||||
# -DCMAKE_INSTALL_PREFIX:PATH=$PWD/out/$SR_ARCH
|
||||
#RUN cd selfrando && make -j`nprocs --all`
|
||||
#RUN cd selfrando && make install
|
||||
|
||||
RUN curl -o /bin/checksec https://raw.githubusercontent.com/slimm609/checksec.sh/2.6.0/checksec && chmod +x /bin/checksec
|
||||
@@ -0,0 +1,34 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-bin-build-tools:latest
|
||||
VERIFY_FILE=actual_verify
|
||||
|
||||
all: build verify
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
# for selfrando...
|
||||
# docker buildx build --platform linux/amd64 -t $(TOOL_IMAGE) .
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
mkdir -p $(BIN)
|
||||
docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
verify: tools
|
||||
@rm -f $(VERIFY_FILE)
|
||||
docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make verify > $(VERIFY_FILE)
|
||||
@python ./differ expected_verify $(VERIFY_FILE)
|
||||
|
||||
debug:
|
||||
docker run -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
cache.fingerprint:
|
||||
@find project Dockerfile Makefile -type f -exec md5sum {} + | awk '{print $1}' | sort | tee cache.fingerprint
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/*
|
||||
|
||||
.PHONY: build verify debug build-image build-bins clean dockerfile-check cache.fingerprint
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r') as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
def diff_json(json1, json2):
|
||||
differences_found = False
|
||||
|
||||
for key in json1:
|
||||
if key not in json2:
|
||||
print(f'missing key "{key}"')
|
||||
continue
|
||||
|
||||
differences = []
|
||||
for subkey in json1[key]:
|
||||
if subkey not in json2[key]:
|
||||
differences.append(f' - "{subkey}": expected "{json1[key][subkey]}" but was MISSING')
|
||||
continue
|
||||
|
||||
if subkey in json2[key] and json1[key][subkey] != json2[key][subkey]:
|
||||
differences.append(f' - "{subkey}": expected "{json1[key][subkey]}" got "{json2[key][subkey]}"')
|
||||
|
||||
if differences:
|
||||
differences_found = True
|
||||
print(f'{key}')
|
||||
for diff in differences:
|
||||
print(diff)
|
||||
print()
|
||||
|
||||
return differences_found
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python ./differ file1.json file2.json")
|
||||
sys.exit(1)
|
||||
|
||||
json1 = load_json(sys.argv[1])
|
||||
json2 = load_json(sys.argv[2])
|
||||
|
||||
if diff_json(json1, json2):
|
||||
print("FAIL: unexpected security feature differences")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("PASS: all security features accounted for")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
{ "dir": { "name":"../bin" }, "../bin/no_protection": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"yes","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/no_protection"}, "../bin/with_nx": { "relro":"no","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_nx"}, "../bin/with_rpath": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"yes","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_rpath"}, "../bin/with_canary": { "relro":"no","canary":"yes","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_canary"}, "../bin/with_relro": { "relro":"full","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_relro"}, "../bin/with_pie": { "relro":"no","canary":"no","nx":"no","pie":"yes","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_pie"}, "../bin/with_runpath": { "relro":"no","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"yes","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_runpath"}, "../bin/with_safestack": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"yes","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"3", "filename":"../bin/with_safestack"}, "../bin/pie_false_positive.so": { "relro":"partial","canary":"no","nx":"yes","pie":"dso","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"0", "filename":"../bin/pie_false_positive.so"}, "../bin/with_cfi": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"yes","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_cfi"}, "../bin/with_partial_relro": { "relro":"partial","canary":"no","nx":"no","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/with_partial_relro"}, "../bin/protected": { "relro":"full","canary":"yes","nx":"yes","pie":"yes","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"no","fortified":"0","fortify-able":"2", "filename":"../bin/protected"}, "../bin/with_fortify": { "relro":"partial","canary":"no","nx":"yes","pie":"no","clangcfi":"no","safestack":"no","rpath":"no","runpath":"no","symbols":"yes","fortify_source":"yes","fortified":"1","fortify-able":"2", "filename":"../bin/with_fortify"}}
|
||||
@@ -0,0 +1,93 @@
|
||||
### GCC Options ############################################
|
||||
CANARY := -fstack-protector
|
||||
NO_CANARY := -fno-stack-protector
|
||||
|
||||
SHARED_OBJ := -shared
|
||||
|
||||
RELRO := -z relro -z now
|
||||
PARTIAL_RELRO := -z relro
|
||||
NO_RELRO := -z norelro
|
||||
|
||||
NX := -z noexecstack
|
||||
NO_NX := -z execstack
|
||||
|
||||
PIE := -fpic -pie
|
||||
NO_PIE := -no-pie
|
||||
|
||||
# deprecated
|
||||
RPATH := -Wl,--disable-new-dtags,-rpath,./libs
|
||||
|
||||
# replaces RPATH (thus us mutually exclusive with it)
|
||||
RUNPATH := -Wl,-rpath,./libs
|
||||
|
||||
GCCFLAGS := -g
|
||||
|
||||
### Clang Options ############################################
|
||||
|
||||
SAFE_STACK := -fsanitize=safe-stack
|
||||
|
||||
CFI := -flto -fvisibility=hidden -fsanitize=cfi
|
||||
|
||||
FORTIFY := -O2 -D_FORTIFY_SOURCE=2
|
||||
|
||||
### Common Options ############################################
|
||||
|
||||
SRC := main.c
|
||||
LIB_SRC := lib.c
|
||||
BIN := ../bin
|
||||
|
||||
BINS := $(BIN)/no_protection $(BIN)/with_nx $(BIN)/pie_false_positive.so $(BIN)/with_pie $(BIN)/with_canary $(BIN)/with_relro $(BIN)/with_partial_relro $(BIN)/with_rpath $(BIN)/with_runpath $(BIN)/with_safestack $(BIN)/with_cfi $(BIN)/with_fortify $(BIN)/protected
|
||||
#.PHONY: verify $(BIN)/no_protection $(BIN)/with_nx $(BIN)/pie_false_positive.so $(BIN)/with_pie $(BIN)/with_canary $(BIN)/with_relro $(BIN)/with_partial_relro $(BIN)/with_rpath $(BIN)/with_runpath $(BIN)/with_safestack $(BIN)/with_cfi $(BIN)/with_fortify $(BIN)/protected
|
||||
.PHONY: verify clean all
|
||||
|
||||
all: $(BINS)
|
||||
|
||||
|
||||
$(BIN)/no_protection : $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RUNPATH)
|
||||
|
||||
$(BIN)/with_nx : $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/pie_false_positive.so: $(LIB_SRC)
|
||||
gcc $< -c -Wall -Werror -fpic $(LIB_SRC)
|
||||
gcc -shared -o $@ lib.o ; rm lib.o
|
||||
|
||||
$(BIN)/with_pie: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(PIE)
|
||||
|
||||
$(BIN)/with_canary: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_relro: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_partial_relro: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(PARTIAL_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/with_rpath: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RPATH)
|
||||
|
||||
$(BIN)/with_runpath: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE) $(RUNPATH)
|
||||
|
||||
$(BIN)/with_safestack: $(SRC)
|
||||
clang $< -o $@ $(SAFE_STACK)
|
||||
|
||||
$(BIN)/with_cfi: $(SRC)
|
||||
clang $< -o $@ $(CFI)
|
||||
|
||||
$(BIN)/with_fortify: $(SRC)
|
||||
clang $< -o $@ $(FORTIFY)
|
||||
|
||||
#$(BIN)/with_selfrando: $(SRC)
|
||||
# srenv gcc $< -o $@ $(GCCFLAGS) $(NO_CANARY) $(NO_NX) $(NO_RELRO) $(NO_PIE)
|
||||
|
||||
$(BIN)/protected: $(SRC)
|
||||
gcc $< -o $@ $(GCCFLAGS) $(CANARY) $(NX) $(RELRO) $(PIE)
|
||||
|
||||
verify:
|
||||
@/bin/checksec --dir=$(BIN) --extended --output=json
|
||||
|
||||
clean:
|
||||
rm -rf $(BINS)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
void foo(void)
|
||||
{
|
||||
puts("Share me!");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef foo_h__
|
||||
#define foo_h__
|
||||
|
||||
extern void foo(void);
|
||||
|
||||
#endif // foo_h__
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// source: https://github.com/trailofbits/clang-cfi-showcase/blob/master/cfi_icall.c
|
||||
|
||||
typedef int (*int_arg_fn)(int);
|
||||
typedef int (*float_arg_fn)(float);
|
||||
|
||||
static int int_arg(int arg) {
|
||||
printf("In %s: (%d)\n", __FUNCTION__, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int float_arg(float arg) {
|
||||
printf("CFI should protect transfer to here\n");
|
||||
printf("In %s: (%f)\n", __FUNCTION__, (double)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bad_int_arg(int arg) {
|
||||
printf("CFI will not protect transfer to here\n");
|
||||
printf("In %s: (%d)\n", __FUNCTION__, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int not_entry_point(int arg) {
|
||||
// nop sled for x86 / x86-64
|
||||
// these instructions act as a buffer
|
||||
// for an indirect control flow transfer to skip
|
||||
// a valid function entry point, but continue
|
||||
// to execute normal code
|
||||
__asm__ volatile (
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
"nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"
|
||||
);
|
||||
printf("CFI ensures control flow only transfers to potentially valid destinations\n");
|
||||
printf("In %s: (%d)\n", __FUNCTION__, arg);
|
||||
// need to exit or the program will segfault anyway,
|
||||
// since the indirect call skipped the function preamble
|
||||
exit(arg);
|
||||
}
|
||||
|
||||
struct foo {
|
||||
int_arg_fn int_funcs[1];
|
||||
int_arg_fn bad_int_funcs[1];
|
||||
float_arg_fn float_funcs[1];
|
||||
int_arg_fn not_entries[1];
|
||||
};
|
||||
|
||||
// the struct aligns the function pointer arrays
|
||||
// so indexing past the end will reliably
|
||||
// call working function pointers
|
||||
static struct foo f = {
|
||||
.int_funcs = {int_arg},
|
||||
.bad_int_funcs = {bad_int_arg},
|
||||
.float_funcs = {float_arg},
|
||||
.not_entries = {(int_arg_fn)((uintptr_t)(not_entry_point)+0x20)}
|
||||
};
|
||||
|
||||
void simple1() {
|
||||
char buf[16];
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
printf(buf);
|
||||
}
|
||||
|
||||
void simple2() {
|
||||
char buf[16];
|
||||
scanf("%s", buf);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc != 2) {
|
||||
printf("Usage: %s <option>\n", argv[0]);
|
||||
printf("Option values:\n");
|
||||
printf("\t0\tCall correct function\n");
|
||||
printf("\t1\tCall the wrong function but with the same signature\n");
|
||||
printf("\t2\tCall a float function with an int function signature\n");
|
||||
printf("\t3\tCall into the middle of a function\n");
|
||||
printf("\n");
|
||||
printf("\tAll other options are undefined, but should be caught by CFI :)\n");
|
||||
printf("\n\n");
|
||||
printf("Here are some pointers so clang doesn't optimize away members of `struct foo f`:\n");
|
||||
printf("\tint_funcs: %p\n", (void*)f.int_funcs);
|
||||
printf("\tbad_int_funcs: %p\n", (void*)f.bad_int_funcs);
|
||||
printf("\tfloat_funcs: %p\n", (void*)f.float_funcs);
|
||||
printf("\tnot_entries: %p\n", (void*)f.not_entries);
|
||||
return 1;
|
||||
}
|
||||
|
||||
simple1();
|
||||
simple2();
|
||||
|
||||
printf("Calling a function:\n");
|
||||
|
||||
int idx = argv[1][0] - '0';
|
||||
|
||||
return f.int_funcs[idx](idx);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package file
|
||||
|
||||
type (
|
||||
ExecutableFormat string
|
||||
RelocationReadOnly string
|
||||
)
|
||||
|
||||
const (
|
||||
ELF ExecutableFormat = "elf"
|
||||
MachO ExecutableFormat = "macho"
|
||||
PE ExecutableFormat = "pe"
|
||||
|
||||
RelocationReadOnlyNone RelocationReadOnly = "none"
|
||||
RelocationReadOnlyPartial RelocationReadOnly = "partial"
|
||||
RelocationReadOnlyFull RelocationReadOnly = "full"
|
||||
)
|
||||
|
||||
type Executable struct {
|
||||
// Format denotes either ELF, Mach-O, or PE
|
||||
Format ExecutableFormat `json:"format" yaml:"format" mapstructure:"format"`
|
||||
|
||||
SecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||
}
|
||||
|
||||
type ELFSecurityFeatures struct {
|
||||
SymbolTableStripped bool `json:"symbolTableStripped" yaml:"symbolTableStripped" mapstructure:"symbolTableStripped"`
|
||||
|
||||
// classic protections
|
||||
|
||||
StackCanary *bool `json:"stackCanary,omitempty" yaml:"stackCanary" mapstructure:"stackCanary"`
|
||||
NoExecutable bool `json:"nx" yaml:"nx" mapstructure:"nx"`
|
||||
RelocationReadOnly RelocationReadOnly `json:"relRO" yaml:"relRO" mapstructure:"relRO"`
|
||||
PositionIndependentExecutable bool `json:"pie" yaml:"pie" mapstructure:"pie"`
|
||||
DynamicSharedObject bool `json:"dso" yaml:"dso" mapstructure:"dso"`
|
||||
|
||||
// LlvmSafeStack represents a compiler-based security mechanism that separates the stack into a safe stack for storing return addresses and other critical data, and an unsafe stack for everything else, to mitigate stack-based memory corruption errors
|
||||
// see https://clang.llvm.org/docs/SafeStack.html
|
||||
LlvmSafeStack *bool `json:"safeStack,omitempty" yaml:"safeStack" mapstructure:"safeStack"`
|
||||
|
||||
// ControlFlowIntegrity represents runtime checks to ensure a program's control flow adheres to the legal paths determined at compile time, thus protecting against various types of control-flow hijacking attacks
|
||||
// see https://clang.llvm.org/docs/ControlFlowIntegrity.html
|
||||
LlvmControlFlowIntegrity *bool `json:"cfi,omitempty" yaml:"cfi" mapstructure:"cfi"`
|
||||
|
||||
// ClangFortifySource is a broad suite of extensions to libc aimed at catching misuses of common library functions
|
||||
// see https://android.googlesource.com/platform//bionic/+/d192dbecf0b2a371eb127c0871f77a9caf81c4d2/docs/clang_fortify_anatomy.md
|
||||
ClangFortifySource *bool `json:"fortify,omitempty" yaml:"fortify" mapstructure:"fortify"`
|
||||
|
||||
//// Selfrando provides function order shuffling to defend against ROP and other types of code reuse
|
||||
//// see https://github.com/runsafesecurity/selfrando
|
||||
// Selfrando *bool `json:"selfrando,omitempty" yaml:"selfrando" mapstructure:"selfrando"`
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
||||
}
|
||||
|
||||
func formatCPE(cpeString string) string {
|
||||
c, err := cpe.New(cpeString)
|
||||
c, err := cpe.NewAttributes(cpeString)
|
||||
if err != nil {
|
||||
log.Debugf("skipping invalid CPE: %s", cpeString)
|
||||
return ""
|
||||
|
||||
@@ -630,7 +630,7 @@ func findPURLValue(p *spdx.Package) string {
|
||||
func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
|
||||
for _, r := range p.PackageExternalReferences {
|
||||
if r.RefType == string(helpers.Cpe23ExternalRefType) {
|
||||
c, err := cpe.New(r.Locator)
|
||||
c, err := cpe.New(r.Locator, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
|
||||
continue
|
||||
|
||||
@@ -28,11 +28,11 @@ func Decoders() []sbom.FormatDecoder {
|
||||
}
|
||||
|
||||
// Identify takes a set of bytes and attempts to identify the format of the SBOM.
|
||||
func Identify(reader io.ReadSeeker) (sbom.FormatID, string) {
|
||||
func Identify(reader io.Reader) (sbom.FormatID, string) {
|
||||
return staticDecoders.Identify(reader)
|
||||
}
|
||||
|
||||
// Decode takes a set of bytes and attempts to decode it into an SBOM.
|
||||
func Decode(reader io.ReadSeeker) (*sbom.SBOM, sbom.FormatID, string, error) {
|
||||
func Decode(reader io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) {
|
||||
return staticDecoders.Decode(reader)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func encodeSingleCPE(p pkg.Package) string {
|
||||
// Since the CPEs in a package are sorted by specificity
|
||||
// we can extract the first CPE as the one to output in cyclonedx
|
||||
if len(p.CPEs) > 0 {
|
||||
return p.CPEs[0].String()
|
||||
return p.CPEs[0].Attributes.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
|
||||
}
|
||||
out = append(out, cyclonedx.Property{
|
||||
Name: "syft:cpe23",
|
||||
Value: c.String(),
|
||||
Value: c.Attributes.String(),
|
||||
})
|
||||
}
|
||||
return
|
||||
@@ -33,7 +33,7 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
|
||||
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
|
||||
if c.CPE != "" {
|
||||
cp, err := cpe.New(c.CPE)
|
||||
cp, err := cpe.New(c.CPE, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", c.CPE)
|
||||
} else {
|
||||
@@ -44,7 +44,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
|
||||
if c.Properties != nil {
|
||||
for _, p := range *c.Properties {
|
||||
if p.Name == "syft:cpe23" {
|
||||
cp, err := cpe.New(p.Value)
|
||||
cp, err := cpe.New(p.Value, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", p.Value)
|
||||
} else {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func Test_encodeCPE(t *testing.T) {
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", "test-source")
|
||||
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*", "test-source-2")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -26,7 +26,7 @@ func Test_encodeCPE(t *testing.T) {
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "single CPE",
|
||||
name: "single Attributes",
|
||||
input: pkg.Package{
|
||||
CPEs: []cpe.CPE{
|
||||
testCPE,
|
||||
|
||||
@@ -222,7 +222,7 @@ func Test_decode(t *testing.T) {
|
||||
if e.cpe != "" {
|
||||
foundCPE := false
|
||||
for _, c := range p.CPEs {
|
||||
cstr := c.BindToFmtString()
|
||||
cstr := c.Attributes.BindToFmtString()
|
||||
if e.cpe == cstr {
|
||||
foundCPE = true
|
||||
break
|
||||
|
||||
@@ -10,7 +10,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, ExternalRef{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: c.String(),
|
||||
ReferenceLocator: c.Attributes.String(),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_ExternalRefs(t *testing.T) {
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", cpe.Source(""))
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -27,7 +27,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
expected: []ExternalRef{
|
||||
{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: testCPE.String(),
|
||||
ReferenceLocator: testCPE.Attributes.String(),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -46,6 +46,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.ErlangOTPPkg:
|
||||
answer = "acquired package info from ErLang application resource file"
|
||||
case pkg.LinuxKernelPkg:
|
||||
answer = "acquired package info from linux kernel archive"
|
||||
case pkg.LinuxKernelModulePkg:
|
||||
|
||||
@@ -199,6 +199,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.ErlangOTPPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from ErLang application resource file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
|
||||
@@ -123,7 +123,7 @@ func newDirectoryCatalog() *pkg.Collection {
|
||||
},
|
||||
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.Source("")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -140,7 +140,7 @@ func newDirectoryCatalog() *pkg.Collection {
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.Source("")),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -175,7 +175,7 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
||||
},
|
||||
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -192,7 +192,7 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", "another-test-source"),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
|
||||
},
|
||||
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -159,11 +159,14 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
Type: "type",
|
||||
CPEs: []cpe.CPE{
|
||||
{
|
||||
Part: "a",
|
||||
Vendor: "vendor",
|
||||
Product: "product",
|
||||
Version: "version",
|
||||
Update: "update",
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "vendor",
|
||||
Product: "product",
|
||||
Version: "version",
|
||||
Update: "update",
|
||||
},
|
||||
Source: "test-source",
|
||||
},
|
||||
},
|
||||
PURL: "pkg:generic/pkg@version",
|
||||
@@ -218,6 +221,22 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Executables: map[file.Coordinates]file.Executable{
|
||||
c: {
|
||||
Format: file.ELF,
|
||||
SecurityFeatures: &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: false,
|
||||
StackCanary: boolRef(true),
|
||||
NoExecutable: false,
|
||||
RelocationReadOnly: "partial",
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(true),
|
||||
ClangFortifySource: boolRef(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
LinuxDistribution: &linux.Release{
|
||||
PrettyName: "some os",
|
||||
Name: "os",
|
||||
|
||||
@@ -152,7 +152,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID string `json:"id"`
|
||||
Location file.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Location file.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||
Executable *file.Executable `json:"executable,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
|
||||
@@ -32,10 +32,17 @@ type PackageBasicData struct {
|
||||
Locations []file.Location `json:"locations"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
CPEs cpes `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
type cpes []CPE
|
||||
|
||||
type CPE struct {
|
||||
Value string `json:"cpe"`
|
||||
Source string `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
type licenses []License
|
||||
|
||||
type License struct {
|
||||
@@ -74,6 +81,29 @@ func (f *licenses) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sourcedCPESfromSimpleCPEs(simpleCPEs []string) []CPE {
|
||||
var result []CPE
|
||||
for _, s := range simpleCPEs {
|
||||
result = append(result, CPE{
|
||||
Value: s,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *cpes) UnmarshalJSON(b []byte) error {
|
||||
var cs []CPE
|
||||
if err := json.Unmarshal(b, &cs); err != nil {
|
||||
var simpleCPEs []string
|
||||
if err := json.Unmarshal(b, &simpleCPEs); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal cpes: %w", err)
|
||||
}
|
||||
cs = sourcedCPESfromSimpleCPEs(simpleCPEs)
|
||||
}
|
||||
*c = cs
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageCustomData struct {
|
||||
MetadataType string `json:"metadataType,omitempty"`
|
||||
|
||||
@@ -386,6 +386,143 @@ func Test_UnmarshalJSON(t *testing.T) {
|
||||
assert.Equal(t, reflect.TypeOf(pkg.RustBinaryAuditEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map-based java metadata",
|
||||
packageData: []byte(`{
|
||||
"id": "e6f845bdaa69ddb2",
|
||||
"name": "SparseBitSet",
|
||||
"version": "1.2",
|
||||
"type": "java-archive",
|
||||
"foundBy": "java-archive-cataloger",
|
||||
"locations": [],
|
||||
"licenses": [],
|
||||
"language": "java",
|
||||
"cpes": [],
|
||||
"purl": "pkg:maven/com.zaxxer/SparseBitSet@1.2",
|
||||
"metadataType": "java-archive",
|
||||
"metadata": {
|
||||
"virtualPath": "/opt/solr-9.4.1/modules/extraction/lib/SparseBitSet-1.2.jar",
|
||||
"manifest": {
|
||||
"main": {
|
||||
"Archiver-Version": "Plexus Archiver",
|
||||
"Build-Jdk": "1.8.0_73",
|
||||
"Built-By": "lbayer",
|
||||
"Created-By": "Apache Maven 3.5.0",
|
||||
"Manifest-Version": "1.0"
|
||||
},
|
||||
"namedSections": {
|
||||
"META-INF/mailcap": {
|
||||
"SHA-256-Digest": "kXN4VupOQOJhduMGwxumj4ijmD/YAlz97a9Mp7CVXtk="
|
||||
},
|
||||
"META-INF/versions/9/module-info.class": {
|
||||
"SHA-256-Digest": "cMeIRa5l8DWPgrVWavr/6TKVBUGixVKGcu6yOTZMlKk="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
meta := p.Metadata.(pkg.JavaArchive)
|
||||
manifest := meta.Manifest
|
||||
assert.Equal(t, "1.8.0_73", manifest.Main.MustGet("Build-Jdk"))
|
||||
require.Equal(t, 2, len(manifest.Sections))
|
||||
assert.Equal(t, "META-INF/mailcap", manifest.Sections[0].MustGet("Name"))
|
||||
assert.Equal(t, "kXN4VupOQOJhduMGwxumj4ijmD/YAlz97a9Mp7CVXtk=", manifest.Sections[0].MustGet("SHA-256-Digest"))
|
||||
assert.Equal(t, "META-INF/versions/9/module-info.class", manifest.Sections[1].MustGet("Name"))
|
||||
assert.Equal(t, "cMeIRa5l8DWPgrVWavr/6TKVBUGixVKGcu6yOTZMlKk=", manifest.Sections[1].MustGet("SHA-256-Digest"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre key-value golang metadata",
|
||||
packageData: []byte(`{
|
||||
"id": "e348ed25484a94c9",
|
||||
"name": "github.com/anchore/syft",
|
||||
"version": "v0.101.1-SNAPSHOT-4c777834",
|
||||
"type": "go-module",
|
||||
"foundBy": "go-module-binary-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/syft",
|
||||
"layerID": "sha256:2274947a5f3527e48d8725a96646aefdcce3d99340c1eefb1e7c894043863c92",
|
||||
"accessPath": "/syft",
|
||||
"annotations": {
|
||||
"evidence": "primary"
|
||||
}
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "go",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:anchore:syft:v0.101.1-SNAPSHOT-4c777834:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:golang/github.com/anchore/syft@v0.101.1-SNAPSHOT-4c777834",
|
||||
"metadataType": "go-module-buildinfo-entry",
|
||||
"metadata": {
|
||||
"goBuildSettings": {
|
||||
"-buildmode": "exe",
|
||||
"-compiler": "gc",
|
||||
"-ldflags": "-w -s -extldflags '-static' -X main.version=0.101.1-SNAPSHOT-4c777834 -X main.gitCommit=4c777834618b2ad8ad94cd200a45d6670bc1c013 -X main.buildDate=2024-01-22T16:43:49Z -X main.gitDescription=v0.101.1-4-g4c777834 ",
|
||||
"CGO_ENABLED": "0",
|
||||
"GOAMD64": "v1",
|
||||
"GOARCH": "amd64",
|
||||
"GOOS": "linux",
|
||||
"vcs": "git",
|
||||
"vcs.modified": "false",
|
||||
"vcs.revision": "4c777834618b2ad8ad94cd200a45d6670bc1c013",
|
||||
"vcs.time": "2024-01-22T16:31:41Z"
|
||||
},
|
||||
"goCompiledVersion": "go1.21.2",
|
||||
"architecture": "amd64",
|
||||
"mainModule": "github.com/anchore/syft"
|
||||
}
|
||||
}
|
||||
`),
|
||||
assert: func(p *Package) {
|
||||
buildInfo := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
|
||||
assert.Equal(t, "exe", buildInfo.BuildSettings.MustGet("-buildmode"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "conan lock with legacy options",
|
||||
packageData: []byte(`{
|
||||
"id": "75eb35307226c921",
|
||||
"name": "boost",
|
||||
"version": "1.75.0",
|
||||
"type": "conan",
|
||||
"foundBy": "conan-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/conan.lock",
|
||||
"accessPath": "/conan.lock",
|
||||
"annotations": {
|
||||
"evidence": "primary"
|
||||
}
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "c++",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:boost:boost:1.75.0:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:conan/boost@1.75.0",
|
||||
"metadataType": "c-conan-lock-entry",
|
||||
"metadata": {
|
||||
"ref": "boost/1.75.0#a9c318f067216f900900e044e7af4ab1",
|
||||
"package_id": "dc8aedd23a0f0a773a5fcdcfe1ae3e89c4205978",
|
||||
"prev": "b9d7912e6131dfa453c725593b36c808",
|
||||
"options": {
|
||||
"addr2line_location": "/usr/bin/addr2line",
|
||||
"asio_no_deprecated": "False",
|
||||
"zstd": "False"
|
||||
},
|
||||
"context": "host"
|
||||
}
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
metadata := p.Metadata.(pkg.ConanV1LockEntry)
|
||||
assert.Equal(t, "False", metadata.Options.MustGet("asio_no_deprecated"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
}
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "python-package",
|
||||
@@ -56,7 +58,9 @@
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
}
|
||||
],
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"source": "nvd-cpe-dictionary"
|
||||
}
|
||||
],
|
||||
"purl": "a-purl-1",
|
||||
"metadataType": "python-package",
|
||||
@@ -51,7 +54,10 @@
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"source": "syft-generated"
|
||||
}
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"source": "syft-generated"
|
||||
}
|
||||
],
|
||||
"purl": "a-purl-1",
|
||||
"metadataType": "python-package",
|
||||
@@ -53,7 +56,10 @@
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
{
|
||||
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"source": "nvd-cpe-dictionary"
|
||||
}
|
||||
],
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
|
||||
@@ -119,13 +119,19 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
})
|
||||
}
|
||||
|
||||
var executable *file.Executable
|
||||
if exec, exists := artifacts.Executables[coordinates]; exists {
|
||||
executable = &exec
|
||||
}
|
||||
|
||||
results = append(results, model.File{
|
||||
ID: string(coordinates.ID()),
|
||||
Location: coordinates,
|
||||
Metadata: toFileMetadataEntry(coordinates, metadata),
|
||||
Digests: digests,
|
||||
Contents: contents,
|
||||
Licenses: licenses,
|
||||
ID: string(coordinates.ID()),
|
||||
Location: coordinates,
|
||||
Metadata: toFileMetadataEntry(coordinates, metadata),
|
||||
Digests: digests,
|
||||
Contents: contents,
|
||||
Licenses: licenses,
|
||||
Executable: executable,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -229,9 +235,13 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
|
||||
// toPackageModel crates a new Package from the given pkg.Package.
|
||||
func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
var cpes = make([]model.CPE, len(p.CPEs))
|
||||
for i, c := range p.CPEs {
|
||||
cpes[i] = c.String()
|
||||
convertedCPE := model.CPE{
|
||||
Value: c.Attributes.String(),
|
||||
Source: c.Source.String(),
|
||||
}
|
||||
cpes[i] = convertedCPE
|
||||
}
|
||||
|
||||
// we want to make sure all catalogers are
|
||||
|
||||
@@ -2,6 +2,8 @@ package syftjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -35,6 +37,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
|
||||
FileDigests: fileArtifacts.FileDigests,
|
||||
FileContents: fileArtifacts.FileContents,
|
||||
FileLicenses: fileArtifacts.FileLicenses,
|
||||
Executables: fileArtifacts.Executables,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
@@ -69,19 +72,18 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
FileDigests: make(map[file.Coordinates][]file.Digest),
|
||||
FileContents: make(map[file.Coordinates]string),
|
||||
FileLicenses: make(map[file.Coordinates][]file.License),
|
||||
Executables: make(map[file.Coordinates]file.Executable),
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
coord := f.Location
|
||||
if f.Metadata != nil {
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64)
|
||||
fm, err := safeFileModeConvert(f.Metadata.Mode)
|
||||
if err != nil {
|
||||
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err)
|
||||
mode = 0
|
||||
fm = 0
|
||||
}
|
||||
|
||||
fm := os.FileMode(mode)
|
||||
|
||||
ret.FileMetadata[coord] = file.Metadata{
|
||||
FileInfo: stereoscopeFile.ManualInfo{
|
||||
NameValue: path.Base(coord.RealPath),
|
||||
@@ -124,11 +126,29 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
LicenseEvidence: evidence,
|
||||
})
|
||||
}
|
||||
|
||||
if f.Executable != nil {
|
||||
ret.Executables[coord] = *f.Executable
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func safeFileModeConvert(val int) (fs.FileMode, error) {
|
||||
if val < math.MinInt32 || val > math.MaxInt32 {
|
||||
// Value is out of the range that int32 can represent
|
||||
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
|
||||
}
|
||||
|
||||
// Safe to convert to os.FileMode
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return os.FileMode(mode), nil
|
||||
}
|
||||
|
||||
func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
for _, l := range m {
|
||||
p = append(p, pkg.License{
|
||||
@@ -301,9 +321,9 @@ func toSyftCatalog(pkgs []model.Package, idAliases map[string]string) *pkg.Colle
|
||||
func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
||||
var cpes []cpe.CPE
|
||||
for _, c := range p.CPEs {
|
||||
value, err := cpe.New(c)
|
||||
value, err := cpe.New(c.Value, cpe.Source(c.Source))
|
||||
if err != nil {
|
||||
log.Warnf("excluding invalid CPE %q: %v", c, err)
|
||||
log.Warnf("excluding invalid Attributes %q: %v", c, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package syftjson
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -230,6 +233,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
want: sbom.Artifacts{
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{},
|
||||
Executables: map[file.Coordinates]file.Executable{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -245,6 +249,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
Executable: nil,
|
||||
},
|
||||
},
|
||||
want: sbom.Artifacts{
|
||||
@@ -257,6 +262,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Executables: map[file.Coordinates]file.Executable{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -280,6 +286,20 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
Executable: &file.Executable{
|
||||
Format: file.ELF,
|
||||
SecurityFeatures: &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: false,
|
||||
StackCanary: boolRef(true),
|
||||
NoExecutable: false,
|
||||
RelocationReadOnly: "partial",
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(true),
|
||||
ClangFortifySource: boolRef(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: sbom.Artifacts{
|
||||
@@ -306,6 +326,22 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Executables: map[file.Coordinates]file.Executable{
|
||||
coord: {
|
||||
Format: file.ELF,
|
||||
SecurityFeatures: &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: false,
|
||||
StackCanary: boolRef(true),
|
||||
NoExecutable: false,
|
||||
RelocationReadOnly: "partial",
|
||||
PositionIndependentExecutable: false,
|
||||
DynamicSharedObject: false,
|
||||
LlvmSafeStack: boolRef(false),
|
||||
LlvmControlFlowIntegrity: boolRef(true),
|
||||
ClangFortifySource: boolRef(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -318,7 +354,11 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toSyfRelationship(t *testing.T) {
|
||||
func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func Test_toSyftRelationship(t *testing.T) {
|
||||
packageWithId := func(id string) *pkg.Package {
|
||||
p := &pkg.Package{}
|
||||
p.OverrideID(artifact.ID(id))
|
||||
@@ -434,3 +474,45 @@ func Test_deduplicateErrors(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_safeFileModeConvert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
val int
|
||||
want fs.FileMode
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
// fs.go ModePerm 511 = FileMode = 0777 // Unix permission bits :192
|
||||
name: "valid perm",
|
||||
val: 777,
|
||||
want: os.FileMode(511), // 777 in octal equals 511 in decimal
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "outside int32 high",
|
||||
val: int(math.MaxInt32) + 1,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "outside int32 low",
|
||||
val: int(math.MinInt32) - 1,
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := safeFileModeConvert(tt.val)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func build() *jsonschema.Schema {
|
||||
// ensure the generated list of names is stable between runs
|
||||
sort.Strings(metadataNames)
|
||||
|
||||
var metadataTypes = []map[string]string{
|
||||
metadataTypes := []map[string]string{
|
||||
// allow for no metadata to be provided
|
||||
{"type": "null"},
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func build() *jsonschema.Schema {
|
||||
}
|
||||
|
||||
func encode(schema *jsonschema.Schema) []byte {
|
||||
var newSchemaBuffer = new(bytes.Buffer)
|
||||
newSchemaBuffer := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(newSchemaBuffer)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
@@ -154,6 +154,7 @@ func write(schema []byte) {
|
||||
os.Exit(1)
|
||||
}
|
||||
schemaPath := filepath.Join(repoRoot, "schema", "json", fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion))
|
||||
latestSchemaPath := filepath.Join(repoRoot, "schema", "json", "schema-latest.json")
|
||||
|
||||
if _, err := os.Stat(schemaPath); !os.IsNotExist(err) {
|
||||
// check if the schema is the same...
|
||||
@@ -182,13 +183,23 @@ func write(schema []byte) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
_, err = fh.Write(schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer fh.Close()
|
||||
latestFile, err := os.Create(latestSchemaPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer latestFile.Close()
|
||||
|
||||
_, err = latestFile.Write(schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Wrote new schema to %q\n", schemaPath)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) {
|
||||
// any definition that is used within another struct should not be considered a top-level metadata definition
|
||||
names.Remove(usedNames.List()...)
|
||||
|
||||
// remove known exceptions, that is, types exported in the pkg Package that are not used
|
||||
// in a metadata type but are not metadata types themselves.
|
||||
names.Remove("Licenses", "KeyValue")
|
||||
|
||||
strNames := names.List()
|
||||
sort.Strings(strNames)
|
||||
|
||||
@@ -114,7 +118,11 @@ func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error)
|
||||
|
||||
structType := extractStructType(spec.Type)
|
||||
if structType == nil {
|
||||
continue
|
||||
// maybe this is a slice of structs? This is useful (say type KeyValues is []KeyValue)
|
||||
structType = extractSliceOfStructType(spec.Type)
|
||||
if structType == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
metadataDefinitions = append(metadataDefinitions, name)
|
||||
@@ -124,6 +132,34 @@ func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error)
|
||||
return metadataDefinitions, usedTypeNames, nil
|
||||
}
|
||||
|
||||
func extractSliceOfStructType(exp ast.Expr) *ast.StructType {
|
||||
var structType *ast.StructType
|
||||
switch ty := exp.(type) {
|
||||
case *ast.ArrayType:
|
||||
// this is a standard definition:
|
||||
// type FooMetadata []BarMetadata
|
||||
structType = extractStructType(ty.Elt)
|
||||
case *ast.Ident:
|
||||
if ty.Obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this might be a type created from another type:
|
||||
// type FooMetadata BarMetadata
|
||||
// ... but we need to check that the other type definition is a struct type
|
||||
typeSpec, ok := ty.Obj.Decl.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
nestedStructType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
structType = nestedStructType
|
||||
}
|
||||
return structType
|
||||
}
|
||||
|
||||
func extractStructType(exp ast.Expr) *ast.StructType {
|
||||
var structType *ast.StructType
|
||||
switch ty := exp.(type) {
|
||||
|
||||
@@ -11,7 +11,8 @@ func AllTypes() []any {
|
||||
pkg.ApkDBEntry{},
|
||||
pkg.BinarySignature{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{},
|
||||
pkg.ConanfileEntry{},
|
||||
pkg.ConaninfoEntry{},
|
||||
pkg.DartPubspecLockEntry{},
|
||||
@@ -36,6 +37,7 @@ func AllTypes() []any {
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPackage{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonPoetryLockEntry{},
|
||||
pkg.PythonRequirementsEntry{},
|
||||
pkg.RDescription{},
|
||||
pkg.RpmArchive{},
|
||||
@@ -44,5 +46,6 @@ func AllTypes() []any {
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"),
|
||||
jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"),
|
||||
jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"),
|
||||
jsonNames(pkg.ConanLockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
|
||||
jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
|
||||
jsonNames(pkg.ConanV2LockEntry{}, "c-conan-lock-v2-entry"),
|
||||
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
|
||||
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
|
||||
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
|
||||
@@ -86,11 +87,13 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.NixStoreEntry{}, "nix-store-entry", "NixStoreMetadata"),
|
||||
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
|
||||
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
|
||||
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
|
||||
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.PortageEntry{}, "portage-db-entry", "PortageMetadata"),
|
||||
jsonNames(pkg.PythonPackage{}, "python-package", "PythonPackageMetadata"),
|
||||
jsonNames(pkg.PythonPipfileLockEntry{}, "python-pipfile-lock-entry", "PythonPipfileLockMetadata"),
|
||||
jsonNames(pkg.PythonPoetryLockEntry{}, "python-poetry-lock-entry", "PythonPoetryLockMetadata"),
|
||||
jsonNames(pkg.PythonRequirementsEntry{}, "python-pip-requirements-entry", "PythonRequirementsMetadata"),
|
||||
jsonNames(pkg.ErlangRebarLockEntry{}, "erlang-rebar-lock-entry", "RebarLockMetadataType"),
|
||||
jsonNames(pkg.RDescription{}, "r-description", "RDescriptionFileMetadataType"),
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
|
||||
{
|
||||
name: "map pkg.ConanLockEntry struct type",
|
||||
input: "ConanLockMetadataType",
|
||||
expected: reflect.TypeOf(pkg.ConanLockEntry{}),
|
||||
expected: reflect.TypeOf(pkg.ConanV1LockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ConanfileEntry struct type",
|
||||
@@ -290,7 +290,7 @@ func Test_JSONName_JSONLegacyName(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ConanLockMetadata",
|
||||
metadata: pkg.ConanLockEntry{},
|
||||
metadata: pkg.ConanV1LockEntry{},
|
||||
expectedJSONName: "c-conan-lock-entry",
|
||||
expectedLegacyName: "ConanLockMetadataType",
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user