mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
wire up cli config
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
41aa6f6753
commit
a05608a4c8
@ -147,6 +147,13 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
c.Content.SkipFilesAboveSize = cfg.File.Content.SkipFilesAboveSize
|
||||
c.Executable.Globs = cfg.File.Executable.Globs
|
||||
|
||||
// symbol capture configuration
|
||||
c.Executable.Symbols.CaptureScope = cfg.File.Executable.Symbols.CaptureScope
|
||||
c.Executable.Symbols.Types = cfg.File.Executable.Symbols.Types
|
||||
c.Executable.Symbols.Go.StandardLibrary = cfg.File.Executable.Symbols.Go.StandardLibrary
|
||||
c.Executable.Symbols.Go.ExtendedStandardLibrary = cfg.File.Executable.Symbols.Go.ExtendedStandardLibrary
|
||||
c.Executable.Symbols.Go.ThirdPartyModules = cfg.File.Executable.Symbols.Go.ThirdPartyModules
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
)
|
||||
|
||||
type fileConfig struct {
|
||||
@ -29,10 +30,26 @@ type fileContent struct {
|
||||
|
||||
type fileExecutable struct {
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
Symbols fileSymbolConfig `yaml:"symbols" json:"symbols" mapstructure:"symbols"`
|
||||
}
|
||||
|
||||
type fileSymbolConfig struct {
|
||||
CaptureScope []executable.SymbolCaptureScope `yaml:"capture" json:"capture" mapstructure:"capture"`
|
||||
Types []string `yaml:"types" json:"types" mapstructure:"types"`
|
||||
Go fileGoSymbolConfig `yaml:"go" json:"go" mapstructure:"go"`
|
||||
}
|
||||
|
||||
type fileGoSymbolConfig struct {
|
||||
StandardLibrary bool `yaml:"standard-library" json:"standard-library" mapstructure:"standard-library"`
|
||||
ExtendedStandardLibrary bool `yaml:"extended-standard-library" json:"extended-standard-library" mapstructure:"extended-standard-library"`
|
||||
ThirdPartyModules bool `yaml:"third-party-modules" json:"third-party-modules" mapstructure:"third-party-modules"`
|
||||
}
|
||||
|
||||
func defaultFileConfig() fileConfig {
|
||||
return fileConfig{
|
||||
api := executable.DefaultConfig()
|
||||
|
||||
// start with API defaults and override CLI-specific values
|
||||
cfg := fileConfig{
|
||||
Metadata: fileMetadata{
|
||||
Selection: file.FilesOwnedByPackageSelection,
|
||||
Digests: []string{"sha1", "sha256"},
|
||||
@ -41,9 +58,19 @@ func defaultFileConfig() fileConfig {
|
||||
SkipFilesAboveSize: 250 * intFile.KB,
|
||||
},
|
||||
Executable: fileExecutable{
|
||||
Globs: nil,
|
||||
Globs: api.Globs,
|
||||
Symbols: fileSymbolConfig{
|
||||
CaptureScope: api.Symbols.CaptureScope,
|
||||
Types: api.Symbols.Types,
|
||||
Go: fileGoSymbolConfig{
|
||||
StandardLibrary: api.Symbols.Go.StandardLibrary,
|
||||
ExtendedStandardLibrary: api.Symbols.Go.ExtendedStandardLibrary,
|
||||
ThirdPartyModules: api.Symbols.Go.ThirdPartyModules,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
@ -75,4 +102,13 @@ Options include:
|
||||
descriptions.Add(&c.Content.Globs, `file globs for the cataloger to match on`)
|
||||
|
||||
descriptions.Add(&c.Executable.Globs, `file globs for the cataloger to match on`)
|
||||
|
||||
// symbol capture configuration
|
||||
descriptions.Add(&c.Executable.Symbols.CaptureScope, `the scope of symbols to capture from executables (options: "golang")`)
|
||||
descriptions.Add(&c.Executable.Symbols.Types, `the types of symbols to capture, relative to "go tool nm" output (options: "T", "t", "R", "r", "D", "d", "B", "b", "C", "U")`)
|
||||
|
||||
// go symbol configuration
|
||||
descriptions.Add(&c.Executable.Symbols.Go.StandardLibrary, `capture Go standard library symbols (e.g. "fmt", "net/http")`)
|
||||
descriptions.Add(&c.Executable.Symbols.Go.ExtendedStandardLibrary, `capture extended Go standard library symbols (e.g. "golang.org/x/net")`)
|
||||
descriptions.Add(&c.Executable.Symbols.Go.ThirdPartyModules, `capture third-party module symbols (e.g. "github.com/spf13/cobra")`)
|
||||
}
|
||||
|
||||
@ -31,7 +31,8 @@ func TestAllPackageCatalogersReachableInTasks(t *testing.T) {
|
||||
taskFactories := task.DefaultPackageTaskFactories()
|
||||
taskTagsByName := make(map[string][]string)
|
||||
for _, factory := range taskFactories {
|
||||
tsk := factory(task.DefaultCatalogingFactoryConfig())
|
||||
tsk, err := factory(task.DefaultCatalogingFactoryConfig())
|
||||
require.NoError(t, err)
|
||||
if taskTagsByName[tsk.Name()] != nil {
|
||||
t.Fatalf("duplicate task name: %q", tsk.Name())
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -8,7 +9,7 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type factory func(cfg CatalogingFactoryConfig) Task
|
||||
type factory func(cfg CatalogingFactoryConfig) (Task, error)
|
||||
|
||||
type Factories []factory
|
||||
|
||||
@ -16,9 +17,13 @@ func (f Factories) Tasks(cfg CatalogingFactoryConfig) ([]Task, error) {
|
||||
var allTasks []Task
|
||||
taskNames := strset.New()
|
||||
duplicateTaskNames := strset.New()
|
||||
var err error
|
||||
var errs []error
|
||||
for _, fact := range f {
|
||||
tsk := fact(cfg)
|
||||
tsk, err := fact(cfg)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if tsk == nil {
|
||||
continue
|
||||
}
|
||||
@ -33,8 +38,8 @@ func (f Factories) Tasks(cfg CatalogingFactoryConfig) ([]Task, error) {
|
||||
if duplicateTaskNames.Size() > 0 {
|
||||
names := duplicateTaskNames.List()
|
||||
sort.Strings(names)
|
||||
err = fmt.Errorf("duplicate cataloger task names: %v", strings.Join(names, ", "))
|
||||
errs = append(errs, fmt.Errorf("duplicate cataloger task names: %v", strings.Join(names, ", ")))
|
||||
}
|
||||
|
||||
return allTasks, err
|
||||
return allTasks, errors.Join(errs...)
|
||||
}
|
||||
|
||||
@ -26,8 +26,8 @@ func DefaultFileTaskFactories() Factories {
|
||||
}
|
||||
|
||||
func newFileDigestCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileDigestCatalogerTask(cfg.FilesConfig.Selection, cfg.FilesConfig.Hashers, tags...)
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return newFileDigestCatalogerTask(cfg.FilesConfig.Selection, cfg.FilesConfig.Hashers, tags...), nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ func newFileDigestCatalogerTask(selection file.Selection, hashers []crypto.Hash,
|
||||
}
|
||||
|
||||
func newFileMetadataCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileMetadataCatalogerTask(cfg.FilesConfig.Selection, tags...)
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return newFileMetadataCatalogerTask(cfg.FilesConfig.Selection, tags...), nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,8 +88,8 @@ func newFileMetadataCatalogerTask(selection file.Selection, tags ...string) Task
|
||||
}
|
||||
|
||||
func newFileContentCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileContentCatalogerTask(cfg.FilesConfig.Content, tags...)
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return newFileContentCatalogerTask(cfg.FilesConfig.Content, tags...), nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,12 +114,16 @@ func newFileContentCatalogerTask(cfg filecontent.Config, tags ...string) Task {
|
||||
}
|
||||
|
||||
func newExecutableCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return newExecutableCatalogerTask(cfg.FilesConfig.Selection, cfg.FilesConfig.Executable, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
func newExecutableCatalogerTask(selection file.Selection, cfg executable.Config, tags ...string) Task {
|
||||
func newExecutableCatalogerTask(selection file.Selection, cfg executable.Config, tags ...string) (Task, error) {
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
@ -136,7 +140,7 @@ func newExecutableCatalogerTask(selection file.Selection, cfg executable.Config,
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn, commonFileTags(tags)...)
|
||||
return NewTask("file-executable-cataloger", fn, commonFileTags(tags)...), nil
|
||||
}
|
||||
|
||||
// TODO: this should be replaced with a fix that allows passing a coordinate or location iterator to the cataloger
|
||||
|
||||
@ -21,14 +21,14 @@ import (
|
||||
)
|
||||
|
||||
func newPackageTaskFactory(catalogerFactory func(CatalogingFactoryConfig) pkg.Cataloger, tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return NewPackageTask(cfg, catalogerFactory(cfg), tags...)
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return NewPackageTask(cfg, catalogerFactory(cfg), tags...), nil
|
||||
}
|
||||
}
|
||||
|
||||
func newSimplePackageTaskFactory(catalogerFactory func() pkg.Cataloger, tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return NewPackageTask(cfg, catalogerFactory(), tags...)
|
||||
return func(cfg CatalogingFactoryConfig) (Task, error) {
|
||||
return NewPackageTask(cfg, catalogerFactory(), tags...), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1285,7 +1285,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Symbols captures the selection from the symbol table found in the binary.\nSymbols []Symbol `json:\"symbols,omitempty\" yaml:\"symbols\" mapstructure:\"symbols\"`"
|
||||
"description": "Symbols captures the selection from the symbol table found in the binary."
|
||||
},
|
||||
"toolchains": {
|
||||
"items": {
|
||||
@ -4238,13 +4238,16 @@
|
||||
"Toolchain": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Name is the name of the toolchain (e.g., \"gcc\", \"clang\", \"ld\", etc.)."
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Version is the version of the toolchain."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Kind indicates the type of toolchain (e.g., compiler, linker, runtime)."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@ -1285,7 +1285,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Symbols captures the selection from the symbol table found in the binary.\nSymbols []Symbol `json:\"symbols,omitempty\" yaml:\"symbols\" mapstructure:\"symbols\"`"
|
||||
"description": "Symbols captures the selection from the symbol table found in the binary."
|
||||
},
|
||||
"toolchains": {
|
||||
"items": {
|
||||
@ -4238,13 +4238,16 @@
|
||||
"Toolchain": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Name is the name of the toolchain (e.g., \"gcc\", \"clang\", \"ld\", etc.)."
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Version is the version of the toolchain."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Kind indicates the type of toolchain (e.g., compiler, linker, runtime)."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@ -24,20 +24,11 @@ import (
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
// SymbolCaptureScope defines the scope of symbols to capture from executables. For the meantime only golang binaries are supported,
|
||||
// however, in the future this can be expanded to include rust audit binaries, libraries only, applications only, or all binaries.
|
||||
type SymbolCaptureScope string
|
||||
|
||||
// type SymbolTypes string
|
||||
|
||||
const (
|
||||
SymbolScopeAll SymbolCaptureScope = "all" // any and all binaries
|
||||
SymbolScopeLibraries SymbolCaptureScope = "libraries" // binaries with exported symbols
|
||||
SymbolScopeApplications SymbolCaptureScope = "applications" // binaries with an entry point
|
||||
SymbolScopeGolang SymbolCaptureScope = "golang" // only binaries built with the golang toolchain
|
||||
SymbolScopeNone SymbolCaptureScope = "none" // do not capture any symbols
|
||||
|
||||
// SymbolTypeCode SymbolTypes = "code"
|
||||
// SymbolTypeData SymbolTypes = "data"
|
||||
)
|
||||
const SymbolScopeGolang SymbolCaptureScope = "golang" // only binaries built with the golang toolchain
|
||||
|
||||
type Config struct {
|
||||
// MIMETypes are the MIME types that will be considered for executable cataloging.
|
||||
@ -90,6 +81,64 @@ type GoSymbolConfig struct {
|
||||
UnexportedSymbols bool `json:"unexported-symbols" yaml:"unexported-symbols" mapstructure:"unexported-symbols"`
|
||||
}
|
||||
|
||||
// Validate checks for logical configuration inconsistencies and returns an error if any are found.
|
||||
func (c Config) Validate() error {
|
||||
return c.Symbols.Validate()
|
||||
}
|
||||
|
||||
// Validate checks for logical configuration inconsistencies in symbol capture settings.
|
||||
func (s SymbolConfig) Validate() error {
|
||||
// validate that all CaptureScope values are valid
|
||||
for _, scope := range s.CaptureScope {
|
||||
if !isValidCaptureScope(scope) {
|
||||
return fmt.Errorf("invalid symbol capture scope %q: valid values are %q", scope, SymbolScopeGolang)
|
||||
}
|
||||
}
|
||||
|
||||
// validate NM types if specified
|
||||
if len(s.Types) > 0 {
|
||||
for _, t := range s.Types {
|
||||
if !isValidNMType(t) {
|
||||
return fmt.Errorf("invalid NM type %q: valid values are %v", t, validNMTypes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remaining validations only apply when Go symbol capture is enabled
|
||||
if !s.hasGolangScope() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if Go symbol capture is enabled, at least one of exported/unexported must be true
|
||||
if !s.Go.ExportedSymbols && !s.Go.UnexportedSymbols {
|
||||
return fmt.Errorf("both exported-symbols and unexported-symbols are disabled; no Go symbols would be captured")
|
||||
}
|
||||
|
||||
// if Go symbol capture is enabled, at least one module source must be enabled
|
||||
if !s.Go.StandardLibrary && !s.Go.ExtendedStandardLibrary && !s.Go.ThirdPartyModules {
|
||||
return fmt.Errorf("all module sources (standard-library, extended-standard-library, third-party-modules) are disabled; no meaningful Go symbols would be captured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SymbolConfig) hasGolangScope() bool {
|
||||
for _, scope := range s.CaptureScope {
|
||||
if scope == SymbolScopeGolang {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidCaptureScope(scope SymbolCaptureScope) bool {
|
||||
switch scope { //nolint:gocritic // lets elect a pattern as if we'll have multiple options in the future...
|
||||
case SymbolScopeGolang:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Cataloger struct {
|
||||
config Config
|
||||
}
|
||||
@ -101,10 +150,8 @@ func DefaultConfig() Config {
|
||||
MIMETypes: m,
|
||||
Globs: nil,
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{
|
||||
SymbolScopeGolang,
|
||||
},
|
||||
Types: []string{"T", "t"},
|
||||
CaptureScope: []SymbolCaptureScope{}, // important! by default we do not capture any symbols unless explicitly configured
|
||||
Types: []string{"T"}, // by default only capture "T" (text/code) symbols, since vulnerability data tracks accessible function symbols
|
||||
Go: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
|
||||
273
syft/file/cataloger/executable/config_test.go
Normal file
273
syft/file/cataloger/executable/config_test.go
Normal file
@ -0,0 +1,273 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func TestDefaultConfig_SymbolCaptureIsDisabled(t *testing.T) {
|
||||
// symbol capture should be disabled by default -- this is an expensive operation space-wise in the SBOM
|
||||
// and should only be enabled when explicitly configured by the user.
|
||||
cfg := DefaultConfig()
|
||||
|
||||
require.Empty(t, cfg.Symbols.CaptureScope, "symbol capture should be disabled by default (empty capture scope)")
|
||||
|
||||
// verify that shouldCaptureSymbols returns false for any executable when using default config
|
||||
assert.False(t, shouldCaptureSymbols(nil, cfg.Symbols), "should not capture symbols for nil executable")
|
||||
assert.False(t, shouldCaptureSymbols(&file.Executable{}, cfg.Symbols), "should not capture symbols for empty executable")
|
||||
assert.False(t, shouldCaptureSymbols(&file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
}, cfg.Symbols), "should not capture symbols even for go binaries when using default config")
|
||||
}
|
||||
|
||||
func TestConfig_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "default config is valid",
|
||||
cfg: DefaultConfig(),
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "valid config with golang scope enabled",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "empty capture scope with Go settings is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "invalid capture scope",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{"invalid-scope"},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "invalid NM type",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
Types: []string{"X", "Y"},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "valid NM types",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
Types: []string{"T", "t", "R"},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "both exported and unexported disabled with golang scope",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: false,
|
||||
UnexportedSymbols: false,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "both exported and unexported disabled without golang scope is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: false,
|
||||
UnexportedSymbols: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "all module sources disabled with golang scope",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
ExtendedStandardLibrary: false,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "all module sources disabled without golang scope is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
ExtendedStandardLibrary: false,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "only standard library enabled is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: false,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "only extended stdlib enabled is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "only third party modules enabled is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
ExtendedStandardLibrary: false,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "only unexported symbols enabled is valid",
|
||||
cfg: Config{
|
||||
Symbols: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: false,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.cfg.Validate()
|
||||
tt.wantErr(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSymbolConfig_Validate_ErrorMessages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg SymbolConfig
|
||||
wantErrContain string
|
||||
}{
|
||||
{
|
||||
name: "invalid capture scope error message",
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{"rust"},
|
||||
},
|
||||
wantErrContain: "invalid symbol capture scope",
|
||||
},
|
||||
{
|
||||
name: "invalid NM type error message",
|
||||
cfg: SymbolConfig{
|
||||
Types: []string{"Z"},
|
||||
},
|
||||
wantErrContain: "invalid NM type",
|
||||
},
|
||||
{
|
||||
name: "both export options disabled error message",
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: false,
|
||||
UnexportedSymbols: false,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
wantErrContain: "both exported-symbols and unexported-symbols are disabled",
|
||||
},
|
||||
{
|
||||
name: "all module sources disabled error message",
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
ExtendedStandardLibrary: false,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
wantErrContain: "all module sources",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.cfg.Validate()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.wantErrContain)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,21 @@ var goNMTypes = []string{
|
||||
"U", // referenced but undefined symbol
|
||||
}
|
||||
|
||||
// validNMTypes returns the list of valid NM types for Go symbols.
|
||||
func validNMTypes() []string {
|
||||
return goNMTypes
|
||||
}
|
||||
|
||||
// isValidNMType checks if the given type is a valid NM type.
|
||||
func isValidNMType(t string) bool {
|
||||
for _, valid := range goNMTypes {
|
||||
if t == valid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
vendorPrefix = "vendor/"
|
||||
extendedStdlibPrefix = "golang.org/x/"
|
||||
|
||||
@ -11,20 +11,7 @@ func shouldCaptureSymbols(data *file.Executable, cfg SymbolConfig) bool {
|
||||
}
|
||||
|
||||
for _, scope := range cfg.CaptureScope {
|
||||
switch scope {
|
||||
case SymbolScopeNone:
|
||||
// explicit "none" means don't capture (but continue checking other scopes)
|
||||
continue
|
||||
case SymbolScopeAll:
|
||||
return true
|
||||
case SymbolScopeLibraries:
|
||||
if data.HasExports {
|
||||
return true
|
||||
}
|
||||
case SymbolScopeApplications:
|
||||
if data.HasEntrypoint {
|
||||
return true
|
||||
}
|
||||
switch scope { //nolint:gocritic // lets elect a pattern as if we'll have multiple options in the future...
|
||||
case SymbolScopeGolang:
|
||||
if hasGolangToolchain(data) {
|
||||
return true
|
||||
|
||||
@ -19,7 +19,7 @@ func TestShouldCaptureSymbols(t *testing.T) {
|
||||
name: "nil data returns false",
|
||||
data: nil,
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
@ -31,62 +31,6 @@ func TestShouldCaptureSymbols(t *testing.T) {
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope none returns false",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeNone},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope all returns true",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope libraries with exports returns true",
|
||||
data: &file.Executable{
|
||||
HasExports: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope libraries without exports returns false",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope applications with entrypoint returns true",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope applications without entrypoint returns false",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope golang with go toolchain returns true",
|
||||
data: &file.Executable{
|
||||
@ -119,38 +63,6 @@ func TestShouldCaptureSymbols(t *testing.T) {
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "multiple scopes with one match returns true",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple scopes with no match returns false",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
HasEntrypoint: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "none scope followed by matching scope returns true",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeNone, SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "go toolchain among multiple toolchains returns true",
|
||||
data: &file.Executable{
|
||||
@ -210,7 +122,6 @@ func TestHasGolangToolchain(t *testing.T) {
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "ld", Version: "2.38", Kind: file.ToolchainKindLinker},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Stage 1: Build binaries for multiple platforms
|
||||
FROM golang:1.24 AS builder
|
||||
FROM golang:1.24
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -8,18 +7,8 @@ RUN go mod download
|
||||
|
||||
COPY main.go ./
|
||||
|
||||
# build ELF (Linux)
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hello_linux .
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /hello_linux .
|
||||
|
||||
# build Mach-O (macOS)
|
||||
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o hello_mac .
|
||||
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o /hello_mac .
|
||||
|
||||
# build PE (Windows)
|
||||
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o hello.exe .
|
||||
|
||||
# Stage 2: Minimal image with just the binaries
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /app/hello_linux /
|
||||
COPY --from=builder /app/hello_mac /
|
||||
COPY --from=builder /app/hello.exe /
|
||||
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o /hello.exe .
|
||||
|
||||
@ -1 +1,6 @@
|
||||
FROM silkeh/clang:18@sha256:6984fdf656b270ff3129e339a25083e0f8355f681b72fdeb5407da21a9bbf74d
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache clang18 musl-dev make
|
||||
|
||||
# create symlink so 'clang' command works (Alpine installs as clang-18)
|
||||
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
|
||||
|
||||
@ -7,15 +7,13 @@ type (
|
||||
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
||||
RelocationReadOnly string
|
||||
|
||||
// SymbolType string
|
||||
|
||||
// ToolchainKind represents the type of toolchain used to build the executable. Today only "compiler" is supported,
|
||||
// however, this can be expanded in the future to include linkers, runtimes, etc.
|
||||
ToolchainKind string
|
||||
)
|
||||
|
||||
const (
|
||||
ToolchainKindCompiler ToolchainKind = "compiler"
|
||||
ToolchainKindLinker ToolchainKind = "linker"
|
||||
ToolchainKindRuntime ToolchainKind = "runtime"
|
||||
|
||||
ELF ExecutableFormat = "elf" // Executable and Linkable Format used on Unix-like systems
|
||||
MachO ExecutableFormat = "macho" // Mach object file format used on macOS and iOS
|
||||
@ -44,7 +42,6 @@ type Executable struct {
|
||||
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||
|
||||
// Symbols captures the selection from the symbol table found in the binary.
|
||||
// Symbols []Symbol `json:"symbols,omitempty" yaml:"symbols" mapstructure:"symbols"`
|
||||
SymbolNames []string `json:"symbolNames,omitempty" yaml:"symbolNames" mapstructure:"symbolNames"`
|
||||
|
||||
// Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
||||
@ -52,8 +49,13 @@ type Executable struct {
|
||||
}
|
||||
|
||||
type Toolchain struct {
|
||||
// Name is the name of the toolchain (e.g., "gcc", "clang", "ld", etc.).
|
||||
Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||
|
||||
// Version is the version of the toolchain.
|
||||
Version string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version"`
|
||||
|
||||
// Kind indicates the type of toolchain (e.g., compiler, linker, runtime).
|
||||
Kind ToolchainKind `json:"kind" yaml:"kind" mapstructure:"kind"`
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user