feat(image): custom docker host option (#3599)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
aswath-s-tw
2023-04-21 00:40:51 +05:30
committed by GitHub
parent cc18f92cf3
commit be47b688c7
47 changed files with 317 additions and 188 deletions

View File

@@ -43,6 +43,7 @@ trivy image [flags] IMAGE_NAME
--custom-headers strings custom headers in client mode --custom-headers strings custom headers in client mode
--db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db") --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db")
--dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages
--docker-host string unix domain socket path to use for docker scanning
--download-db-only download/update vulnerability database but don't run a scan --download-db-only download/update vulnerability database but don't run a scan
--download-java-db-only download/update Java index database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan
--enable-modules strings [EXPERIMENTAL] module names to enable --enable-modules strings [EXPERIMENTAL] module names to enable

View File

@@ -193,6 +193,15 @@ image:
# Same as '--removed-pkgs' # Same as '--removed-pkgs'
# Default is false # Default is false
removed-pkgs: false removed-pkgs: false
# Same as '--platform'
# Default is empty
platform:
docker:
# Same as '--docker-host'
# Default is empty
host:
``` ```
## Vulnerability Options ## Vulnerability Options

View File

@@ -482,3 +482,9 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
</details> </details>
### Configure Docker daemon socket to connect to.
You can configure Docker daemon socket with `DOCKER_HOST` or `--docker-host`.
```shell
$ trivy image --docker-host tcp://127.0.0.1:2375 YOUR_IMAGE
```

View File

@@ -760,7 +760,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
if err != nil { if err != nil {
return xerrors.Errorf("flag error: %w", err) return xerrors.Errorf("flag error: %w", err)
} }
return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.Remote()) return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.Registry())
}, },
}, },
&cobra.Command{ &cobra.Command{

View File

@@ -22,7 +22,7 @@ import (
// initializeDockerScanner is for container image scanning in standalone mode // initializeDockerScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
localArtifactCache cache.LocalArtifactCache, remoteOpt types.RemoteOptions, artifactOption artifact.Option) ( localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneDockerSet) wire.Build(scanner.StandaloneDockerSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
@@ -69,7 +69,7 @@ func initializeVMScanner(ctx context.Context, filePath string, artifactCache cac
// initializeRemoteDockerScanner is for container image scanning in client/server mode // initializeRemoteDockerScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
remoteScanOptions client.ScannerOption, remoteOpt types.RemoteOptions, artifactOption artifact.Option) ( remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteDockerSet) wire.Build(scanner.RemoteDockerSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil

View File

@@ -19,6 +19,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/cache"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/javadb" "github.com/aquasecurity/trivy/pkg/javadb"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
@@ -314,7 +315,7 @@ func (r *runner) initDB(ctx context.Context, opts flag.Options) error {
// download the database file // download the database file
noProgress := opts.Quiet || opts.NoProgress noProgress := opts.Quiet || opts.NoProgress
if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.Remote()); err != nil { if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.Registry()); err != nil {
return err return err
} }
@@ -615,8 +616,6 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
fileChecksum = true fileChecksum = true
} }
remoteOpts := opts.Remote()
return ScannerConfig{ return ScannerConfig{
Target: target, Target: target,
ArtifactCache: cacheClient, ArtifactCache: cacheClient,
@@ -633,18 +632,25 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
FilePatterns: opts.FilePatterns, FilePatterns: opts.FilePatterns,
Offline: opts.OfflineScan, Offline: opts.OfflineScan,
NoProgress: opts.NoProgress || opts.Quiet, NoProgress: opts.NoProgress || opts.Quiet,
Insecure: opts.Insecure,
RepoBranch: opts.RepoBranch, RepoBranch: opts.RepoBranch,
RepoCommit: opts.RepoCommit, RepoCommit: opts.RepoCommit,
RepoTag: opts.RepoTag, RepoTag: opts.RepoTag,
SBOMSources: opts.SBOMSources, SBOMSources: opts.SBOMSources,
RekorURL: opts.RekorURL, RekorURL: opts.RekorURL,
Platform: opts.Platform, Platform: opts.Platform,
DockerHost: opts.DockerHost,
Slow: opts.Slow, Slow: opts.Slow,
AWSRegion: opts.Region, AWSRegion: opts.Region,
FileChecksum: fileChecksum, FileChecksum: fileChecksum,
// For OCI registries // For image scanning
RemoteOptions: remoteOpts, ImageOption: ftypes.ImageOptions{
RegistryOptions: opts.Registry(),
DockerOptions: ftypes.DockerOptions{
Host: opts.DockerHost,
},
},
// For misconfiguration scanning // For misconfiguration scanning
MisconfScannerOption: configScannerOptions, MisconfScannerOption: configScannerOptions,

View File

@@ -12,7 +12,7 @@ import (
// $ trivy image alpine:3.15 // $ trivy image alpine:3.15
func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
conf.ArtifactOption.RemoteOptions, conf.ArtifactOption) conf.ArtifactOption.ImageOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
} }
@@ -34,7 +34,7 @@ func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.
func imageRemoteScanner(ctx context.Context, conf ScannerConfig) ( func imageRemoteScanner(ctx context.Context, conf ScannerConfig) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, s, cleanup, err := initializeRemoteDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption,
conf.ArtifactOption.RemoteOptions, conf.ArtifactOption) conf.ArtifactOption.ImageOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote docker scanner: %w", err) return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote docker scanner: %w", err)
} }

View File

@@ -30,14 +30,14 @@ import (
// initializeDockerScanner is for container image scanning in standalone mode // initializeDockerScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, remoteOpt types.RemoteOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache) applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{} detector := ospkg.Detector{}
config := db.Config{} config := db.Config{}
client := vulnerability.NewClient(config) client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, detector, client) localScanner := local.NewScanner(applierApplier, detector, client)
v := _wireValue v := _wireValue
typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, remoteOpt, v...) typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt, v...)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
@@ -140,11 +140,11 @@ func initializeVMScanner(ctx context.Context, filePath string, artifactCache cac
// initializeRemoteDockerScanner is for container image scanning in client/server mode // initializeRemoteDockerScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, remoteOpt types.RemoteOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue2 v := _wireValue2
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
v2 := _wireValue3 v2 := _wireValue3
typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, remoteOpt, v2...) typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt, v2...)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }

View File

@@ -109,7 +109,7 @@ func (c Cache) ClearArtifacts() error {
} }
// DownloadDB downloads the DB // DownloadDB downloads the DB
func DownloadDB(ctx context.Context, appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt types.RemoteOptions) error { func DownloadDB(ctx context.Context, appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt types.RegistryOptions) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()

View File

@@ -35,7 +35,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
// download the database file // download the database file
if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository,
true, opts.SkipDBUpdate, opts.Remote()); err != nil { true, opts.SkipDBUpdate, opts.Registry()); err != nil {
return err return err
} }
@@ -58,6 +58,6 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
m.Register() m.Register()
server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader,
opts.DBRepository, opts.Remote()) opts.DBRepository, opts.Registry())
return server.ListenAndServe(cache, opts.SkipDBUpdate) return server.ListenAndServe(cache, opts.SkipDBUpdate)
} }

View File

@@ -25,7 +25,7 @@ const (
// Operation defines the DB operations // Operation defines the DB operations
type Operation interface { type Operation interface {
NeedsUpdate(cliVersion string, skip bool) (need bool, err error) NeedsUpdate(cliVersion string, skip bool) (need bool, err error)
Download(ctx context.Context, dst string, opt types.RemoteOptions) (err error) Download(ctx context.Context, dst string, opt types.RegistryOptions) (err error)
} }
type options struct { type options struct {
@@ -143,7 +143,7 @@ func (c *Client) isNewDB(meta metadata.Metadata) bool {
} }
// Download downloads the DB file // Download downloads the DB file
func (c *Client) Download(ctx context.Context, dst string, opt types.RemoteOptions) error { func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOptions) error {
// Remove the metadata file under the cache directory before downloading DB // Remove the metadata file under the cache directory before downloading DB
if err := c.metadata.Delete(); err != nil { if err := c.metadata.Delete(); err != nil {
log.Logger.Debug("no metadata file") log.Logger.Debug("no metadata file")
@@ -183,7 +183,7 @@ func (c *Client) updateDownloadedAt(dst string) error {
return nil return nil
} }
func (c *Client) initOCIArtifact(opt types.RemoteOptions) (*oci.Artifact, error) { func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, error) {
if c.artifact != nil { if c.artifact != nil {
return c.artifact, nil return c.artifact, nil
} }

View File

@@ -219,7 +219,7 @@ func TestClient_Download(t *testing.T) {
}, nil) }, nil)
// Mock OCI artifact // Mock OCI artifact
opt := ftypes.RemoteOptions{ opt := ftypes.RegistryOptions{
Insecure: false, Insecure: false,
} }
art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img)) art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img))

View File

@@ -53,7 +53,7 @@ func (_m *MockOperation) ApplyDownloadExpectations(expectations []OperationDownl
} }
// Download provides a mock function with given fields: ctx, dst // Download provides a mock function with given fields: ctx, dst
func (_m *MockOperation) Download(ctx context.Context, dst string, opt types.RemoteOptions) error { func (_m *MockOperation) Download(ctx context.Context, dst string, opt types.RegistryOptions) error {
ret := _m.Called(ctx, dst, opt) ret := _m.Called(ctx, dst, opt)
var r0 error var r0 error

View File

@@ -18,11 +18,13 @@ type Option struct {
SkipDirs []string SkipDirs []string
FilePatterns []string FilePatterns []string
NoProgress bool NoProgress bool
Insecure bool
Offline bool Offline bool
AppDirs []string AppDirs []string
SBOMSources []string SBOMSources []string
RekorURL string RekorURL string
Platform string Platform string
DockerHost string
Slow bool // Lower CPU and memory Slow bool // Lower CPU and memory
AWSRegion string AWSRegion string
FileChecksum bool // For SPDX FileChecksum bool // For SPDX
@@ -32,8 +34,8 @@ type Option struct {
RepoCommit string RepoCommit string
RepoTag string RepoTag string
// For OCI registries // For image scanning
types.RemoteOptions ImageOption types.ImageOptions
MisconfScannerOption misconf.ScannerOption MisconfScannerOption misconf.ScannerOption
SecretScannerOption analyzer.SecretScannerOption SecretScannerOption analyzer.SecretScannerOption

View File

@@ -59,7 +59,7 @@ func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactRe
} }
// Fetch referrers // Fetch referrers
index, err := remote.Referrers(ctx, digest, a.artifactOption.RemoteOptions) index, err := remote.Referrers(ctx, digest, a.artifactOption.ImageOption.RegistryOptions)
if err != nil { if err != nil {
return ftypes.ArtifactReference{}, xerrors.Errorf("unable to fetch referrers: %w", err) return ftypes.ArtifactReference{}, xerrors.Errorf("unable to fetch referrers: %w", err)
} }
@@ -81,7 +81,7 @@ func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactRe
func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (ftypes.ArtifactReference, error) { func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (ftypes.ArtifactReference, error) {
const fileName string = "referrer.sbom" const fileName string = "referrer.sbom"
repoName := fmt.Sprintf("%s@%s", repo, desc.Digest) repoName := fmt.Sprintf("%s@%s", repo, desc.Digest)
referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.RemoteOptions) referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.ImageOption.RegistryOptions)
if err != nil { if err != nil {
return ftypes.ArtifactReference{}, xerrors.Errorf("OCI error: %w", err) return ftypes.ArtifactReference{}, xerrors.Errorf("OCI error: %w", err)
} }

View File

@@ -9,8 +9,8 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func tryDockerDaemon(imageName string, ref name.Reference) (types.Image, func(), error) { func tryDockerDaemon(imageName string, ref name.Reference, opt types.DockerOptions) (types.Image, func(), error) {
img, cleanup, err := daemon.DockerImage(ref) img, cleanup, err := daemon.DockerImage(ref, opt.Host)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@@ -11,10 +11,19 @@ import (
// DockerImage implements v1.Image by extending daemon.Image. // DockerImage implements v1.Image by extending daemon.Image.
// The caller must call cleanup() to remove a temporary file. // The caller must call cleanup() to remove a temporary file.
func DockerImage(ref name.Reference) (Image, func(), error) { func DockerImage(ref name.Reference, host string) (Image, func(), error) {
cleanup := func() {} cleanup := func() {}
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) opts := []client.Opt{
client.FromEnv,
client.WithAPIVersionNegotiation(),
}
if host != "" {
// adding host parameter to the last assuming it will pick up more preference
opts = append(opts, client.WithHost(host))
}
c, err := client.NewClientWithOpts(opts...)
if err != nil { if err != nil {
return nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err) return nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err)
} }

View File

@@ -40,7 +40,7 @@ func TestDockerImage(t *testing.T) {
ref, err := name.ParseReference(tt.imageName) ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err) require.NoError(t, err)
_, cleanup, err := DockerImage(ref) _, cleanup, err := DockerImage(ref, "")
assert.Equal(t, tt.wantErr, err != nil, err) assert.Equal(t, tt.wantErr, err != nil, err)
defer func() { defer func() {
if cleanup != nil { if cleanup != nil {

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"runtime"
"testing" "testing"
"time" "time"
@@ -17,18 +19,19 @@ import (
"github.com/aquasecurity/testdocker/engine" "github.com/aquasecurity/testdocker/engine"
) )
func TestMain(m *testing.M) { var imagePaths = map[string]string{
imagePaths := map[string]string{
"alpine:3.10": "../../test/testdata/alpine-310.tar.gz", "alpine:3.10": "../../test/testdata/alpine-310.tar.gz",
"alpine:3.11": "../../test/testdata/alpine-311.tar.gz", "alpine:3.11": "../../test/testdata/alpine-311.tar.gz",
"gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz", "gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz",
} }
// for Docker // for Docker
opt := engine.Option{ var opt = engine.Option{
APIVersion: "1.38", APIVersion: "1.38",
ImagePaths: imagePaths, ImagePaths: imagePaths,
} }
func TestMain(m *testing.M) {
te := engine.NewDockerEngine(opt) te := engine.NewDockerEngine(opt)
defer te.Close() defer te.Close()
@@ -59,7 +62,7 @@ func Test_image_ConfigName(t *testing.T) {
ref, err := name.ParseReference(tt.imageName) ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err) require.NoError(t, err)
img, cleanup, err := DockerImage(ref) img, cleanup, err := DockerImage(ref, "")
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()
@@ -70,6 +73,50 @@ func Test_image_ConfigName(t *testing.T) {
} }
} }
func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) {
ref, err := name.ParseReference("alpine:3.11")
require.NoError(t, err)
eo := engine.Option{
APIVersion: opt.APIVersion,
ImagePaths: opt.ImagePaths,
}
var dockerHostParam string
if runtime.GOOS != "windows" {
runtimeDir, err := ioutil.TempDir("", "daemon")
require.NoError(t, err)
dir := filepath.Join(runtimeDir, "image")
err = os.MkdirAll(dir, os.ModePerm)
require.NoError(t, err)
customDockerHost := filepath.Join(dir, "image-test-unix-socket.sock")
eo.UnixDomainSocket = customDockerHost
dockerHostParam = "unix://" + customDockerHost
}
te := engine.NewDockerEngine(eo)
defer te.Close()
if runtime.GOOS == "windows" {
dockerHostParam = te.Listener.Addr().Network() + "://" + te.Listener.Addr().String()
}
img, cleanup, err := DockerImage(ref, dockerHostParam)
require.NoError(t, err)
defer cleanup()
conf, err := img.ConfigName()
assert.Equal(t, v1.Hash{
Algorithm: "sha256",
Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
}, conf)
assert.Nil(t, err)
}
func Test_image_ConfigFile(t *testing.T) { func Test_image_ConfigFile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@@ -156,7 +203,7 @@ func Test_image_ConfigFile(t *testing.T) {
ref, err := name.ParseReference(tt.imageName) ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err) require.NoError(t, err)
img, cleanup, err := DockerImage(ref) img, cleanup, err := DockerImage(ref, "")
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()
@@ -201,7 +248,7 @@ func Test_image_LayerByDiffID(t *testing.T) {
ref, err := name.ParseReference(tt.imageName) ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err) require.NoError(t, err)
img, cleanup, err := DockerImage(ref) img, cleanup, err := DockerImage(ref, "")
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()
@@ -230,7 +277,7 @@ func Test_image_RawConfigFile(t *testing.T) {
ref, err := name.ParseReference(tt.imageName) ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err) require.NoError(t, err)
img, cleanup, err := DockerImage(ref) img, cleanup, err := DockerImage(ref, "")
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()

View File

@@ -44,7 +44,7 @@ func DisableRemote() Option {
} }
} }
func NewContainerImage(ctx context.Context, imageName string, opt types.RemoteOptions, opts ...Option) (types.Image, func(), error) { func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOptions, opts ...Option) (types.Image, func(), error) {
o := &options{ o := &options{
dockerd: true, dockerd: true,
podman: true, podman: true,
@@ -57,7 +57,7 @@ func NewContainerImage(ctx context.Context, imageName string, opt types.RemoteOp
var errs error var errs error
var nameOpts []name.Option var nameOpts []name.Option
if opt.Insecure { if opt.RegistryOptions.Insecure {
nameOpts = append(nameOpts, name.Insecure) nameOpts = append(nameOpts, name.Insecure)
} }
ref, err := name.ParseReference(imageName, nameOpts...) ref, err := name.ParseReference(imageName, nameOpts...)
@@ -67,7 +67,7 @@ func NewContainerImage(ctx context.Context, imageName string, opt types.RemoteOp
// Try accessing Docker Daemon // Try accessing Docker Daemon
if o.dockerd { if o.dockerd {
img, cleanup, err := tryDockerDaemon(imageName, ref) img, cleanup, err := tryDockerDaemon(imageName, ref, opt.DockerOptions)
if err == nil { if err == nil {
// Return v1.Image if the image is found in Docker Engine // Return v1.Image if the image is found in Docker Engine
return img, cleanup, nil return img, cleanup, nil
@@ -97,7 +97,7 @@ func NewContainerImage(ctx context.Context, imageName string, opt types.RemoteOp
// Try accessing Docker Registry // Try accessing Docker Registry
if o.remote { if o.remote {
img, err := tryRemote(ctx, imageName, ref, opt) img, err := tryRemote(ctx, imageName, ref, opt.RegistryOptions)
if err == nil { if err == nil {
// Return v1.Image if the image is found in a remote registry // Return v1.Image if the image is found in a remote registry
return img, func() {}, nil return img, func() {}, nil

View File

@@ -54,7 +54,7 @@ func TestNewDockerImage(t *testing.T) {
type args struct { type args struct {
imageName string imageName string
option types.RemoteOptions option types.ImageOptions
} }
tests := []struct { tests := []struct {
name string name string
@@ -205,7 +205,8 @@ func TestNewDockerImage(t *testing.T) {
name: "happy path with insecure Docker Registry", name: "happy path with insecure Docker Registry",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "test", Username: "test",
@@ -215,6 +216,7 @@ func TestNewDockerImage(t *testing.T) {
Insecure: true, Insecure: true,
}, },
}, },
},
wantID: "sha256:af341ccd2df8b0e2d67cf8dd32e087bfda4e5756ebd1c76bbf3efa0dc246590e", wantID: "sha256:af341ccd2df8b0e2d67cf8dd32e087bfda4e5756ebd1c76bbf3efa0dc246590e",
wantRepoTags: []string{serverAddr + "/library/alpine:3.10"}, wantRepoTags: []string{serverAddr + "/library/alpine:3.10"},
wantRepoDigests: []string{ wantRepoDigests: []string{
@@ -331,7 +333,7 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) {
type args struct { type args struct {
imageName string imageName string
option types.RemoteOptions option types.ImageOptions
} }
tests := []struct { tests := []struct {
name string name string
@@ -343,7 +345,8 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) {
name: "happy path with private Docker Registry", name: "happy path with private Docker Registry",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "test", Username: "test",
@@ -354,16 +357,19 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) {
}, },
}, },
}, },
},
{ {
name: "happy path with registry token", name: "happy path with registry token",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
RegistryToken: registryToken, RegistryToken: registryToken,
Insecure: true, Insecure: true,
}, },
}, },
}, },
},
{ {
name: "sad path without a credential", name: "sad path without a credential",
args: args{ args: args{
@@ -375,11 +381,13 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) {
name: "sad path with invalid registry token", name: "sad path with invalid registry token",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.11", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.11", serverAddr),
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
RegistryToken: registryToken + "invalid", RegistryToken: registryToken + "invalid",
Insecure: true, Insecure: true,
}, },
}, },
},
wantErr: "signature is invalid", wantErr: "signature is invalid",
}, },
} }
@@ -501,7 +509,7 @@ func TestDockerPlatformArguments(t *testing.T) {
serverAddr := tr.Listener.Addr().String() serverAddr := tr.Listener.Addr().String()
type args struct { type args struct {
option types.RemoteOptions option types.ImageOptions
} }
tests := []struct { tests := []struct {
name string name string
@@ -512,7 +520,8 @@ func TestDockerPlatformArguments(t *testing.T) {
{ {
name: "happy path with valid platform", name: "happy path with valid platform",
args: args{ args: args{
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "test", Username: "test",
@@ -524,6 +533,7 @@ func TestDockerPlatformArguments(t *testing.T) {
}, },
}, },
}, },
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -15,7 +15,7 @@ type Registry struct {
const azureURL = "azurecr.io" const azureURL = "azurecr.io"
func (r *Registry) CheckOptions(domain string, _ types.RemoteOptions) error { func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) error {
if !strings.HasSuffix(domain, azureURL) { if !strings.HasSuffix(domain, azureURL) {
return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern)
} }

View File

@@ -28,7 +28,7 @@ func TestRegistry_CheckOptions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := azure.Registry{} r := azure.Registry{}
err := r.CheckOptions(tt.domain, types.RemoteOptions{}) err := r.CheckOptions(tt.domain, types.RegistryOptions{})
if tt.wantErr != "" { if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr) assert.EqualError(t, err, tt.wantErr)
} else { } else {

View File

@@ -22,7 +22,7 @@ type ECR struct {
Client ecriface.ECRAPI Client ecriface.ECRAPI
} }
func getSession(option types.RemoteOptions) (*session.Session, error) { func getSession(option types.RegistryOptions) (*session.Session, error) {
// create custom credential information if option is valid // create custom credential information if option is valid
if option.AWSSecretKey != "" && option.AWSAccessKey != "" && option.AWSRegion != "" { if option.AWSSecretKey != "" && option.AWSAccessKey != "" && option.AWSRegion != "" {
return session.NewSessionWithOptions( return session.NewSessionWithOptions(
@@ -45,7 +45,7 @@ func getSession(option types.RemoteOptions) (*session.Session, error) {
}) })
} }
func (e *ECR) CheckOptions(domain string, option types.RemoteOptions) error { func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error {
if !strings.HasSuffix(domain, ecrURL) { if !strings.HasSuffix(domain, ecrURL) {
return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) return xerrors.Errorf("ECR : %w", types.InvalidURLPattern)
} }

View File

@@ -30,7 +30,7 @@ func TestCheckOptions(t *testing.T) {
for testname, v := range tests { for testname, v := range tests {
a := &ECR{} a := &ECR{}
err := a.CheckOptions(v.domain, types.RemoteOptions{}) err := a.CheckOptions(v.domain, types.RegistryOptions{})
if err != nil { if err != nil {
if !errors.Is(err, v.wantErr) { if !errors.Is(err, v.wantErr) {
t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err) t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err)

View File

@@ -24,7 +24,7 @@ const gcrURL = "gcr.io"
// Google artifact registry // Google artifact registry
const garURL = "docker.pkg.dev" const garURL = "docker.pkg.dev"
func (g *Registry) CheckOptions(domain string, option types.RemoteOptions) error { func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) error {
if !strings.HasSuffix(domain, gcrURL) && !strings.HasSuffix(domain, garURL) { if !strings.HasSuffix(domain, gcrURL) && !strings.HasSuffix(domain, garURL) {
return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern)
} }

View File

@@ -13,23 +13,21 @@ import (
func TestCheckOptions(t *testing.T) { func TestCheckOptions(t *testing.T) {
var tests = map[string]struct { var tests = map[string]struct {
domain string domain string
opt types.RemoteOptions opt types.RegistryOptions
gcr *Registry gcr *Registry
wantErr error wantErr error
}{ }{
"InvalidURL": { "InvalidURL": {
domain: "alpine:3.9", domain: "alpine:3.9",
opt: types.RemoteOptions{},
wantErr: types.InvalidURLPattern, wantErr: types.InvalidURLPattern,
}, },
"NoOption": { "NoOption": {
domain: "gcr.io", domain: "gcr.io",
opt: types.RemoteOptions{},
gcr: &Registry{domain: "gcr.io"}, gcr: &Registry{domain: "gcr.io"},
}, },
"CredOption": { "CredOption": {
domain: "gcr.io", domain: "gcr.io",
opt: types.RemoteOptions{GCPCredPath: "/path/to/file.json"}, opt: types.RegistryOptions{GCPCredPath: "/path/to/file.json"},
gcr: &Registry{ gcr: &Registry{
domain: "gcr.io", domain: "gcr.io",
Store: store.NewGCRCredStore("/path/to/file.json"), Store: store.NewGCRCredStore("/path/to/file.json"),

View File

@@ -23,7 +23,7 @@ func init() {
} }
type Registry interface { type Registry interface {
CheckOptions(domain string, option types.RemoteOptions) error CheckOptions(domain string, option types.RegistryOptions) error
GetCredential(ctx context.Context) (string, string, error) GetCredential(ctx context.Context) (string, string, error)
} }
@@ -31,7 +31,7 @@ func RegisterRegistry(registry Registry) {
registries = append(registries, registry) registries = append(registries, registry)
} }
func GetToken(ctx context.Context, domain string, opt types.RemoteOptions) (auth authn.Basic) { func GetToken(ctx context.Context, domain string, opt types.RegistryOptions) (auth authn.Basic) {
// check registry which particular to get credential // check registry which particular to get credential
for _, registry := range registries { for _, registry := range registries {
err := registry.CheckOptions(domain, opt) err := registry.CheckOptions(domain, opt)

View File

@@ -14,7 +14,7 @@ import (
func TestGetToken(t *testing.T) { func TestGetToken(t *testing.T) {
type args struct { type args struct {
domain string domain string
opt types.RemoteOptions opt types.RegistryOptions
} }
tests := []struct { tests := []struct {
name string name string

View File

@@ -12,7 +12,7 @@ import (
"github.com/aquasecurity/trivy/pkg/remote" "github.com/aquasecurity/trivy/pkg/remote"
) )
func tryRemote(ctx context.Context, imageName string, ref name.Reference, option types.RemoteOptions) (types.Image, error) { func tryRemote(ctx context.Context, imageName string, ref name.Reference, option types.RegistryOptions) (types.Image, error) {
desc, err := remote.Get(ctx, ref, option) desc, err := remote.Get(ctx, ref, option)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -233,7 +233,7 @@ func TestContainerd_SearchLocalStoreByNameOrDigest(t *testing.T) {
} }
}) })
img, cleanup, err := image.NewContainerImage(ctx, tt.searchName, types.RemoteOptions{}, img, cleanup, err := image.NewContainerImage(ctx, tt.searchName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote()) image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
defer cleanup() defer cleanup()
if tt.expectErr { if tt.expectErr {
@@ -679,7 +679,7 @@ func localImageTestWithNamespace(t *testing.T, namespace string) {
require.NoError(t, err) require.NoError(t, err)
// Enable only containerd // Enable only containerd
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.RemoteOptions{}, img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote()) image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()
@@ -814,7 +814,7 @@ func TestContainerd_PullImage(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Enable only containerd // Enable only containerd
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.RemoteOptions{}, img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote()) image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()

View File

@@ -151,7 +151,7 @@ func TestFanal_Library_DockerLessMode(t *testing.T) {
}) })
// Enable only registry scanning // Enable only registry scanning
img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.RemoteOptions{}, img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableContainerd()) image.DisableDockerd(), image.DisablePodman(), image.DisableContainerd())
require.NoError(t, err) require.NoError(t, err)
defer cleanup() defer cleanup()
@@ -200,7 +200,7 @@ func TestFanal_Library_DockerMode(t *testing.T) {
require.NoError(t, err, tt.name) require.NoError(t, err, tt.name)
// Enable only dockerd scanning // Enable only dockerd scanning
img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.RemoteOptions{}, img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{},
image.DisablePodman(), image.DisableContainerd(), image.DisableRemote()) image.DisablePodman(), image.DisableContainerd(), image.DisableRemote())
require.NoError(t, err, tt.name) require.NoError(t, err, tt.name)
defer cleanup() defer cleanup()

View File

@@ -12,8 +12,6 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -21,6 +19,7 @@ import (
testcontainers "github.com/testcontainers/testcontainers-go" testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait" "github.com/testcontainers/testcontainers-go/wait"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all"
"github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
@@ -83,7 +82,7 @@ func TestTLSRegistry(t *testing.T) {
name string name string
imageName string imageName string
imageFile string imageFile string
option types.RemoteOptions option types.ImageOptions
login bool login bool
expectedOS types.OS expectedOS types.OS
expectedRepo types.Repository expectedRepo types.Repository
@@ -93,7 +92,8 @@ func TestTLSRegistry(t *testing.T) {
name: "happy path", name: "happy path",
imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310",
imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: registryUsername, Username: registryUsername,
@@ -102,6 +102,7 @@ func TestTLSRegistry(t *testing.T) {
}, },
Insecure: true, Insecure: true,
}, },
},
expectedOS: types.OS{ expectedOS: types.OS{
Name: "3.10.2", Name: "3.10.2",
Family: "alpine", Family: "alpine",
@@ -116,9 +117,11 @@ func TestTLSRegistry(t *testing.T) {
name: "happy path with docker login", name: "happy path with docker login",
imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310",
imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Insecure: true, Insecure: true,
}, },
},
login: true, login: true,
expectedOS: types.OS{ expectedOS: types.OS{
Name: "3.10.2", Name: "3.10.2",
@@ -134,7 +137,8 @@ func TestTLSRegistry(t *testing.T) {
name: "sad path: tls verify", name: "sad path: tls verify",
imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310",
imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: registryUsername, Username: registryUsername,
@@ -142,15 +146,18 @@ func TestTLSRegistry(t *testing.T) {
}, },
}, },
}, },
},
wantErr: true, wantErr: true,
}, },
{ {
name: "sad path: no credential", name: "sad path: no credential",
imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310",
imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
option: types.RemoteOptions{ option: types.ImageOptions{
RegistryOptions: types.RegistryOptions{
Insecure: true, Insecure: true,
}, },
},
wantErr: true, wantErr: true,
}, },
} }
@@ -200,7 +207,7 @@ func getRegistryURL(ctx context.Context, registryC testcontainers.Container, exp
return url.Parse(urlStr) return url.Parse(urlStr)
} }
func analyze(ctx context.Context, imageRef string, opt types.RemoteOptions) (*types.ArtifactDetail, error) { func analyze(ctx context.Context, imageRef string, opt types.ImageOptions) (*types.ArtifactDetail, error) {
d, err := ioutil.TempDir("", "TestRegistry-*") d, err := ioutil.TempDir("", "TestRegistry-*")
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -13,3 +13,50 @@ type ImageExtension interface {
RepoTags() []string RepoTags() []string
RepoDigests() []string RepoDigests() []string
} }
type ImageOptions struct {
RegistryOptions RegistryOptions
DockerOptions DockerOptions
PodmanOptions PodmanOptions
ContainerdOptions ContainerdOptions
}
type DockerOptions struct {
Host string
}
type PodmanOptions struct {
// TODO
}
type ContainerdOptions struct {
// TODO
}
type RegistryOptions struct {
// Auth for registries
Credentials []Credential
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string
// SSL/TLS
Insecure bool
// Architecture
Platform string
// ECR
AWSAccessKey string
AWSSecretKey string
AWSSessionToken string
AWSRegion string
// GCP
GCPCredPath string
}
type Credential struct {
Username string
Password string
}

View File

@@ -1,29 +0,0 @@
package types
type RemoteOptions struct {
// Auth for registries
Credentials []Credential
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string
// SSL/TLS
Insecure bool
// Architecture
Platform string
// ECR
AWSAccessKey string
AWSSecretKey string
AWSSessionToken string
AWSRegion string
// GCP
GCPCredPath string
}
type Credential struct {
Username string
Password string
}

View File

@@ -36,6 +36,12 @@ var (
Value: "", Value: "",
Usage: "set platform in the form os/arch if image is multi-platform capable", Usage: "set platform in the form os/arch if image is multi-platform capable",
} }
DockerHostFlag = Flag{
Name: "docker-host",
ConfigName: "image.docker.host",
Value: "",
Usage: "unix domain socket path to use for docker scanning",
}
) )
type ImageFlagGroup struct { type ImageFlagGroup struct {
@@ -43,6 +49,7 @@ type ImageFlagGroup struct {
ImageConfigScanners *Flag ImageConfigScanners *Flag
ScanRemovedPkgs *Flag ScanRemovedPkgs *Flag
Platform *Flag Platform *Flag
DockerHost *Flag
} }
type ImageOptions struct { type ImageOptions struct {
@@ -50,6 +57,7 @@ type ImageOptions struct {
ImageConfigScanners types.Scanners ImageConfigScanners types.Scanners
ScanRemovedPkgs bool ScanRemovedPkgs bool
Platform string Platform string
DockerHost string
} }
func NewImageFlagGroup() *ImageFlagGroup { func NewImageFlagGroup() *ImageFlagGroup {
@@ -58,6 +66,7 @@ func NewImageFlagGroup() *ImageFlagGroup {
ImageConfigScanners: &ImageConfigScannersFlag, ImageConfigScanners: &ImageConfigScannersFlag,
ScanRemovedPkgs: &ScanRemovedPkgsFlag, ScanRemovedPkgs: &ScanRemovedPkgsFlag,
Platform: &PlatformFlag, Platform: &PlatformFlag,
DockerHost: &DockerHostFlag,
} }
} }
@@ -66,7 +75,13 @@ func (f *ImageFlagGroup) Name() string {
} }
func (f *ImageFlagGroup) Flags() []*Flag { func (f *ImageFlagGroup) Flags() []*Flag {
return []*Flag{f.Input, f.ImageConfigScanners, f.ScanRemovedPkgs, f.Platform} return []*Flag{
f.Input,
f.ImageConfigScanners,
f.ScanRemovedPkgs,
f.Platform,
f.DockerHost,
}
} }
func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) {
@@ -79,5 +94,6 @@ func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) {
ImageConfigScanners: scanners, ImageConfigScanners: scanners,
ScanRemovedPkgs: getBool(f.ScanRemovedPkgs), ScanRemovedPkgs: getBool(f.ScanRemovedPkgs),
Platform: getString(f.Platform), Platform: getString(f.Platform),
DockerHost: getString(f.DockerHost),
}, nil }, nil
} }

View File

@@ -122,9 +122,9 @@ func (o *Options) Align() {
} }
} }
// Remote returns options for OCI registries // Registry returns options for OCI registries
func (o *Options) Remote() ftypes.RemoteOptions { func (o *Options) Registry() ftypes.RegistryOptions {
return ftypes.RemoteOptions{ return ftypes.RegistryOptions{
Credentials: o.Credentials, Credentials: o.Credentials,
RegistryToken: o.RegistryToken, RegistryToken: o.RegistryToken,
Insecure: o.Insecure, Insecure: o.Insecure,

View File

@@ -54,7 +54,7 @@ func (u *Updater) Update() error {
// TODO: support remote options // TODO: support remote options
var a *oci.Artifact var a *oci.Artifact
if a, err = oci.NewArtifact(u.repo, u.quiet, ftypes.RemoteOptions{Insecure: u.insecure}); err != nil { if a, err = oci.NewArtifact(u.repo, u.quiet, ftypes.RegistryOptions{Insecure: u.insecure}); err != nil {
return xerrors.Errorf("oci error: %w", err) return xerrors.Errorf("oci error: %w", err)
} }
if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil { if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil {

View File

@@ -16,7 +16,7 @@ import (
const mediaType = "application/vnd.module.wasm.content.layer.v1+wasm" const mediaType = "application/vnd.module.wasm.content.layer.v1+wasm"
// Install installs a module // Install installs a module
func Install(ctx context.Context, dir, repo string, quiet bool, opt types.RemoteOptions) error { func Install(ctx context.Context, dir, repo string, quiet bool, opt types.RegistryOptions) error {
ref, err := name.ParseReference(repo) ref, err := name.ParseReference(repo)
if err != nil { if err != nil {
return xerrors.Errorf("repository parse error: %w", err) return xerrors.Errorf("repository parse error: %w", err)

View File

@@ -51,17 +51,17 @@ type Artifact struct {
quiet bool quiet bool
// For OCI registries // For OCI registries
types.RemoteOptions types.RegistryOptions
image v1.Image // For testing image v1.Image // For testing
} }
// NewArtifact returns a new artifact // NewArtifact returns a new artifact
func NewArtifact(repo string, quiet bool, remoteOpt types.RemoteOptions, opts ...Option) (*Artifact, error) { func NewArtifact(repo string, quiet bool, registryOpt types.RegistryOptions, opts ...Option) (*Artifact, error) {
art := &Artifact{ art := &Artifact{
repository: repo, repository: repo,
quiet: quiet, quiet: quiet,
RemoteOptions: remoteOpt, RegistryOptions: registryOpt,
} }
for _, o := range opts { for _, o := range opts {
@@ -70,7 +70,7 @@ func NewArtifact(repo string, quiet bool, remoteOpt types.RemoteOptions, opts ..
return art, nil return art, nil
} }
func (a *Artifact) populate(ctx context.Context, opt types.RemoteOptions) error { func (a *Artifact) populate(ctx context.Context, opt types.RegistryOptions) error {
if a.image != nil { if a.image != nil {
return nil return nil
} }
@@ -96,7 +96,7 @@ type DownloadOption struct {
} }
func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) error { func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) error {
if err := a.populate(ctx, a.RemoteOptions); err != nil { if err := a.populate(ctx, a.RegistryOptions); err != nil {
return err return err
} }
@@ -191,7 +191,7 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s
} }
func (a *Artifact) Digest(ctx context.Context) (string, error) { func (a *Artifact) Digest(ctx context.Context) (string, error) {
if err := a.populate(ctx, a.RemoteOptions); err != nil { if err := a.populate(ctx, a.RegistryOptions); err != nil {
return "", err return "", err
} }

View File

@@ -118,7 +118,7 @@ func TestArtifact_Download(t *testing.T) {
}, },
}, nil) }, nil)
artifact, err := oci.NewArtifact("repo", true, ftypes.RemoteOptions{}, oci.WithImage(img)) artifact, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img))
require.NoError(t, err) require.NoError(t, err)
err = artifact.Download(context.Background(), tempDir, oci.DownloadOption{ err = artifact.Download(context.Background(), tempDir, oci.DownloadOption{

View File

@@ -79,7 +79,7 @@ func NewClient(cacheDir string, quiet bool, opts ...Option) (*Client, error) {
func (c *Client) populateOCIArtifact() error { func (c *Client) populateOCIArtifact() error {
if c.artifact == nil { if c.artifact == nil {
repo := fmt.Sprintf("%s:%d", bundleRepository, bundleVersion) repo := fmt.Sprintf("%s:%d", bundleRepository, bundleVersion)
art, err := oci.NewArtifact(repo, c.quiet, types.RemoteOptions{}) art, err := oci.NewArtifact(repo, c.quiet, types.RegistryOptions{})
if err != nil { if err != nil {
return xerrors.Errorf("OCI artifact error: %w", err) return xerrors.Errorf("OCI artifact error: %w", err)
} }

View File

@@ -116,7 +116,7 @@ func TestClient_LoadBuiltinPolicies(t *testing.T) {
}, nil) }, nil)
// Mock OCI artifact // Mock OCI artifact
art, err := oci.NewArtifact("repo", true, ftypes.RemoteOptions{}, oci.WithImage(img)) art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img))
require.NoError(t, err) require.NoError(t, err)
c, err := policy.NewClient(tt.cacheDir, true, policy.WithOCIArtifact(art)) c, err := policy.NewClient(tt.cacheDir, true, policy.WithOCIArtifact(art))
@@ -257,7 +257,7 @@ func TestClient_NeedsUpdate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
art, err := oci.NewArtifact("repo", true, ftypes.RemoteOptions{}, oci.WithImage(img)) art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img))
require.NoError(t, err) require.NoError(t, err)
c, err := policy.NewClient(tmpDir, true, policy.WithOCIArtifact(art), policy.WithClock(tt.clock)) c, err := policy.NewClient(tmpDir, true, policy.WithOCIArtifact(art), policy.WithClock(tt.clock))
@@ -361,7 +361,7 @@ func TestClient_DownloadBuiltinPolicies(t *testing.T) {
}, nil) }, nil)
// Mock OCI artifact // Mock OCI artifact
art, err := oci.NewArtifact("repo", true, ftypes.RemoteOptions{}, oci.WithImage(img)) art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img))
require.NoError(t, err) require.NoError(t, err)
c, err := policy.NewClient(tempDir, true, policy.WithClock(tt.clock), policy.WithOCIArtifact(art)) c, err := policy.NewClient(tempDir, true, policy.WithClock(tt.clock), policy.WithOCIArtifact(art))

View File

@@ -26,7 +26,7 @@ type Descriptor = remote.Descriptor
// Get is a wrapper of google/go-containerregistry/pkg/v1/remote.Get // Get is a wrapper of google/go-containerregistry/pkg/v1/remote.Get
// so that it can try multiple authentication methods. // so that it can try multiple authentication methods.
func Get(ctx context.Context, ref name.Reference, option types.RemoteOptions) (*Descriptor, error) { func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) (*Descriptor, error) {
transport := httpTransport(option.Insecure) transport := httpTransport(option.Insecure)
var errs error var errs error
@@ -63,7 +63,7 @@ func Get(ctx context.Context, ref name.Reference, option types.RemoteOptions) (*
// Image is a wrapper of google/go-containerregistry/pkg/v1/remote.Image // Image is a wrapper of google/go-containerregistry/pkg/v1/remote.Image
// so that it can try multiple authentication methods. // so that it can try multiple authentication methods.
func Image(ctx context.Context, ref name.Reference, option types.RemoteOptions) (v1.Image, error) { func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions) (v1.Image, error) {
transport := httpTransport(option.Insecure) transport := httpTransport(option.Insecure)
var errs error var errs error
@@ -87,7 +87,7 @@ func Image(ctx context.Context, ref name.Reference, option types.RemoteOptions)
// Referrers is a wrapper of google/go-containerregistry/pkg/v1/remote.Referrers // Referrers is a wrapper of google/go-containerregistry/pkg/v1/remote.Referrers
// so that it can try multiple authentication methods. // so that it can try multiple authentication methods.
func Referrers(ctx context.Context, d name.Digest, option types.RemoteOptions) (*v1.IndexManifest, error) { func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) (*v1.IndexManifest, error) {
transport := httpTransport(option.Insecure) transport := httpTransport(option.Insecure)
var errs error var errs error
@@ -121,7 +121,7 @@ func httpTransport(insecure bool) *http.Transport {
} }
} }
func authOptions(ctx context.Context, ref name.Reference, option types.RemoteOptions) []remote.Option { func authOptions(ctx context.Context, ref name.Reference, option types.RegistryOptions) []remote.Option {
var opts []remote.Option var opts []remote.Option
for _, cred := range option.Credentials { for _, cred := range option.Credentials {
opts = append(opts, remote.WithAuth(&authn.Basic{ opts = append(opts, remote.WithAuth(&authn.Basic{

View File

@@ -62,7 +62,7 @@ func TestGet(t *testing.T) {
type args struct { type args struct {
imageName string imageName string
config string config string
option types.RemoteOptions option types.RegistryOptions
} }
tests := []struct { tests := []struct {
name string name string
@@ -74,7 +74,7 @@ func TestGet(t *testing.T) {
name: "single credential", name: "single credential",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "test", Username: "test",
@@ -89,7 +89,7 @@ func TestGet(t *testing.T) {
name: "multiple credential", name: "multiple credential",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "foo", Username: "foo",
@@ -109,7 +109,7 @@ func TestGet(t *testing.T) {
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("test", "testpass")), config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("test", "testpass")),
option: types.RemoteOptions{ option: types.RegistryOptions{
Insecure: true, Insecure: true,
}, },
}, },
@@ -118,7 +118,7 @@ func TestGet(t *testing.T) {
name: "platform", name: "platform",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "test", Username: "test",
@@ -134,7 +134,7 @@ func TestGet(t *testing.T) {
name: "bad credential", name: "bad credential",
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
option: types.RemoteOptions{ option: types.RegistryOptions{
Credentials: []types.Credential{ Credentials: []types.Credential{
{ {
Username: "foo", Username: "foo",
@@ -151,7 +151,7 @@ func TestGet(t *testing.T) {
args: args{ args: args{
imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr),
config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("foo", "bar")), config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("foo", "bar")),
option: types.RemoteOptions{ option: types.RegistryOptions{
Insecure: true, Insecure: true,
}, },
}, },

View File

@@ -35,11 +35,11 @@ type Server struct {
dbRepository string dbRepository string
// For OCI registries // For OCI registries
types.RemoteOptions types.RegistryOptions
} }
// NewServer returns an instance of Server // NewServer returns an instance of Server
func NewServer(appVersion, addr, cacheDir, token, tokenHeader, dbRepository string, opt types.RemoteOptions) Server { func NewServer(appVersion, addr, cacheDir, token, tokenHeader, dbRepository string, opt types.RegistryOptions) Server {
return Server{ return Server{
appVersion: appVersion, appVersion: appVersion,
addr: addr, addr: addr,
@@ -47,7 +47,7 @@ func NewServer(appVersion, addr, cacheDir, token, tokenHeader, dbRepository stri
token: token, token: token,
tokenHeader: tokenHeader, tokenHeader: tokenHeader,
dbRepository: dbRepository, dbRepository: dbRepository,
RemoteOptions: opt, RegistryOptions: opt,
} }
} }
@@ -61,7 +61,7 @@ func (s Server) ListenAndServe(serverCache cache.Cache, skipDBUpdate bool) error
ctx := context.Background() ctx := context.Background()
for { for {
time.Sleep(updateInterval) time.Sleep(updateInterval)
if err := worker.update(ctx, s.appVersion, s.cacheDir, skipDBUpdate, dbUpdateWg, requestWg, s.RemoteOptions); err != nil { if err := worker.update(ctx, s.appVersion, s.cacheDir, skipDBUpdate, dbUpdateWg, requestWg, s.RegistryOptions); err != nil {
log.Logger.Errorf("%+v\n", err) log.Logger.Errorf("%+v\n", err)
} }
} }
@@ -126,7 +126,7 @@ func newDBWorker(dbClient dbFile.Operation) dbWorker {
} }
func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string,
skipDBUpdate bool, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RemoteOptions) error { skipDBUpdate bool, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error {
log.Logger.Debug("Check for DB update...") log.Logger.Debug("Check for DB update...")
needsUpdate, err := w.dbClient.NeedsUpdate(appVersion, skipDBUpdate) needsUpdate, err := w.dbClient.NeedsUpdate(appVersion, skipDBUpdate)
if err != nil { if err != nil {
@@ -142,7 +142,7 @@ func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string,
return nil return nil
} }
func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RemoteOptions) error { func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error {
tmpDir, err := os.MkdirTemp("", "db") tmpDir, err := os.MkdirTemp("", "db")
if err != nil { if err != nil {
return xerrors.Errorf("failed to create a temp dir: %w", err) return xerrors.Errorf("failed to create a temp dir: %w", err)

View File

@@ -161,7 +161,7 @@ func Test_dbWorker_update(t *testing.T) {
var dbUpdateWg, requestWg sync.WaitGroup var dbUpdateWg, requestWg sync.WaitGroup
err := w.update(context.Background(), tt.args.appVersion, cacheDir, err := w.update(context.Background(), tt.args.appVersion, cacheDir,
tt.needsUpdate.input.skip, &dbUpdateWg, &requestWg, ftypes.RemoteOptions{}) tt.needsUpdate.input.skip, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{})
if tt.wantErr != "" { if tt.wantErr != "" {
require.NotNil(t, err, tt.name) require.NotNil(t, err, tt.name)
assert.Contains(t, err.Error(), tt.wantErr, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name)