Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(image): add support for passing architecture and OS #3012

Merged
42 changes: 42 additions & 0 deletions docs/docs/vulnerability/examples/others.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,48 @@ If your image contains lock files which are not maintained by you, you can skip
$ trivy image --skip-dirs /var/lib/gems/2.5.0/gems/fluent-plugin-detect-exceptions-0.0.13 --skip-dirs "/var/lib/gems/2.5.0/gems/http_parser.rb-0.6.0" quay.io/fluentd_elasticsearch/fluentd:v2.9.0
```

## Scan Image on a specific Architecture and OS

By default, Trivy loads an image on a "linux/amd64" machine.
To customise this, pass a `--platform` argument in the format OS/Architecture for the image:

```
$ trivy image --platform=os/architecture [YOUR_IMAGE_NAME]
```

For example:

```
$ trivy image --platform=linux/arm alpine:3.16.1
```

<details>
<summary>Result</summary>

```
2022-10-25T21:00:50.972+0300 INFO Vulnerability scanning is enabled
2022-10-25T21:00:50.972+0300 INFO Secret scanning is enabled
2022-10-25T21:00:50.972+0300 INFO If your scanning is slow, please try '--security-checks vuln' to disable secret scanning
2022-10-25T21:00:50.972+0300 INFO Please see also https://aquasecurity.github.io/trivy/dev/docs/secret/scanning/#recommendation for faster secret detection
2022-10-25T21:00:56.190+0300 INFO Detected OS: alpine
2022-10-25T21:00:56.190+0300 INFO Detecting Alpine vulnerabilities...
2022-10-25T21:00:56.191+0300 INFO Number of language-specific files: 0

alpine:3.16.1 (alpine 3.16.1)
=============================
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)

┌─────────┬────────────────┬──────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
├─────────┼────────────────┼──────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ zlib │ CVE-2022-37434 │ CRITICAL │ 1.2.12-r1 │ 1.2.12-r2 │ zlib: heap-based buffer over-read and overflow in inflate() │
│ │ │ │ │ │ in inflate.c via a... │
│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-37434 │
└─────────┴────────────────┴──────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘
```

</details>

## File patterns
When a directory is given as an input, Trivy will recursively look for and test all files based on file patterns.
The default file patterns are [here](../../misconfiguration/custom/index.md).
Expand Down
1 change: 0 additions & 1 deletion docs/docs/vulnerability/scanning/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,3 @@ Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

</details>


2 changes: 2 additions & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
VulnType: opts.VulnType,
SecurityChecks: opts.SecurityChecks,
ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand
Platform: opts.Platform, // this is valid only for 'image' subcommand
ListAllPackages: opts.ListAllPkgs,
LicenseCategories: opts.LicenseCategories,
FilePatterns: opts.FilePatterns,
Expand Down Expand Up @@ -517,6 +518,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
RepoTag: opts.RepoTag,
SBOMSources: opts.SBOMSources,
RekorURL: opts.RekorURL,
Platform: opts.Platform,

// For misconfiguration scanning
MisconfScannerOption: configScannerOptions,
Expand Down
4 changes: 2 additions & 2 deletions pkg/commands/artifact/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// imageStandaloneScanner initializes a container image scanner in standalone mode
// $ trivy image alpine:3.15
func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS, conf.ArtifactOption.Platform)
if err != nil {
return scanner.Scanner{}, nil, err
}
Expand Down Expand Up @@ -40,7 +40,7 @@ func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.
func imageRemoteScanner(ctx context.Context, conf ScannerConfig) (
scanner.Scanner, func(), error) {
// Scan an image in Docker Engine, Docker Registry, etc.
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS, conf.ArtifactOption.Platform)
if err != nil {
return scanner.Scanner{}, nil, err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Option struct {
RepoTag string
SBOMSources []string
RekorURL string
Platform string

MisconfScannerOption misconf.ScannerOption
SecretScannerOption analyzer.SecretScannerOption
Expand Down
45 changes: 45 additions & 0 deletions pkg/fanal/image/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,48 @@ func TestNewArchiveImage(t *testing.T) {
})
}
}

func TestDockerPlatformArguments(t *testing.T) {
tr := setupPrivateRegistry()
defer tr.Close()

serverAddr := tr.Listener.Addr().String()

type args struct {
option types.DockerOption
}
tests := []struct {
name string
args args
want v1.Image
wantErr string
}{
{
name: "happy path with valid platform",
args: args{
option: types.DockerOption{
UserName: "test",
Password: "testpass",
NonSSL: true,
InsecureSkipTLSVerify: true,
Platform: "arm/linux",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imageName := fmt.Sprintf("%s/library/alpine:3.10", serverAddr)

_, cleanup, err := NewContainerImage(context.Background(), imageName, tt.args.option)
defer cleanup()

if tt.wantErr != "" {
assert.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr, err)
} else {
assert.NoError(t, err)
}
})
}
}
8 changes: 8 additions & 0 deletions pkg/fanal/image/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ func tryRemote(ctx context.Context, imageName string, ref name.Reference, option
remoteOpts = append(remoteOpts, remote.WithTransport(t))
}

if option.Platform != "" {
s, err := v1.ParsePlatform(option.Platform)
if err != nil {
return nil, err
}
remoteOpts = append(remoteOpts, remote.WithPlatform(*s))
}

domain := ref.Context().RegistryStr()
auth := token.GetToken(ctx, domain, option)

Expand Down
3 changes: 3 additions & 0 deletions pkg/fanal/types/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ type DockerOption struct {
// SSL/TLS
InsecureSkipTLSVerify bool
NonSSL bool

// Architecture
Platform string
}
13 changes: 12 additions & 1 deletion pkg/flag/image_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,32 @@ var (
Value: "",
Usage: "input file path instead of image name",
}

PlatformFlag = Flag{
Name: "platform",
ConfigName: "image.platform",
Value: "",
Usage: "set platform in the form os/arch if image is multi-platform capable",
}
)

type ImageFlagGroup struct {
Input *Flag // local image archive
ScanRemovedPkgs *Flag
Platform *Flag
}

type ImageOptions struct {
Input string
ScanRemovedPkgs bool
Platform string
}

func NewImageFlagGroup() *ImageFlagGroup {
return &ImageFlagGroup{
Input: &InputFlag,
ScanRemovedPkgs: &ScanRemovedPkgsFlag,
Platform: &PlatformFlag,
}
}

Expand All @@ -42,12 +52,13 @@ func (f *ImageFlagGroup) Name() string {
}

func (f *ImageFlagGroup) Flags() []*Flag {
return []*Flag{f.Input, f.ScanRemovedPkgs}
return []*Flag{f.Input, f.ScanRemovedPkgs, f.Platform}
}

func (f *ImageFlagGroup) ToOptions() ImageOptions {
return ImageOptions{
Input: getString(f.Input),
ScanRemovedPkgs: getBool(f.ScanRemovedPkgs),
Platform: getString(f.Platform),
}
}
3 changes: 2 additions & 1 deletion pkg/types/docker_conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type DockerConfig struct {
}

// GetDockerOption returns the Docker scanning options using DockerConfig
func GetDockerOption(insecureTlsSkip bool) (types.DockerOption, error) {
func GetDockerOption(insecureTlsSkip bool, Platform string) (types.DockerOption, error) {
cfg := DockerConfig{}
if err := env.Parse(&cfg); err != nil {
return types.DockerOption{}, xerrors.Errorf("unable to parse environment variables: %w", err)
Expand All @@ -28,5 +28,6 @@ func GetDockerOption(insecureTlsSkip bool) (types.DockerOption, error) {
RegistryToken: cfg.RegistryToken,
InsecureSkipTLSVerify: insecureTlsSkip,
NonSSL: cfg.NonSSL,
Platform: Platform,
}, nil
}
1 change: 1 addition & 0 deletions pkg/types/scanoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type ScanOptions struct {
VulnType []string
SecurityChecks []string
ScanRemovedPackages bool
Platform string
ListAllPackages bool
LicenseCategories map[types.LicenseCategory][]string
FilePatterns []string
Expand Down