mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 06:43:05 -08:00
refactor: move from urfave/cli to spf13/cobra (#2458)
Co-authored-by: afdesk <work@afdesk.com> Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
This commit is contained in:
@@ -31,7 +31,6 @@ linters:
|
|||||||
- ineffassign
|
- ineffassign
|
||||||
- typecheck
|
- typecheck
|
||||||
- govet
|
- govet
|
||||||
- errcheck
|
|
||||||
- varcheck
|
- varcheck
|
||||||
- deadcode
|
- deadcode
|
||||||
- revive
|
- revive
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
"github.com/aquasecurity/trivy/pkg/commands"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
)
|
)
|
||||||
@@ -13,8 +11,7 @@ var (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := commands.NewApp(version)
|
app := commands.NewApp(version)
|
||||||
err := app.Run(os.Args)
|
if err := app.Execute(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
go.mod
41
go.mod
@@ -36,30 +36,37 @@ require (
|
|||||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||||
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
|
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0
|
||||||
|
github.com/liamg/memoryfs v1.4.2
|
||||||
|
github.com/liamg/tml v0.6.0
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
|
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/open-policy-agent/opa v0.41.0
|
github.com/open-policy-agent/opa v0.41.0
|
||||||
github.com/owenrumney/go-sarif/v2 v2.1.1
|
github.com/owenrumney/go-sarif/v2 v2.1.1
|
||||||
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
|
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
|
||||||
github.com/samber/lo v1.21.0
|
github.com/samber/lo v1.24.0
|
||||||
github.com/sosedoff/gitkit v0.3.0
|
github.com/sosedoff/gitkit v0.3.0
|
||||||
|
github.com/spf13/cobra v1.4.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/spf13/viper v1.8.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
github.com/testcontainers/testcontainers-go v0.13.0
|
github.com/testcontainers/testcontainers-go v0.13.0
|
||||||
github.com/tetratelabs/wazero v0.0.0-20220701105919-891761ac1ee2
|
github.com/tetratelabs/wazero v0.0.0-20220701105919-891761ac1ee2
|
||||||
github.com/twitchtv/twirp v8.1.2+incompatible
|
github.com/twitchtv/twirp v8.1.2+incompatible
|
||||||
github.com/urfave/cli/v2 v2.8.1
|
|
||||||
github.com/xlab/treeprint v1.1.0
|
github.com/xlab/treeprint v1.1.0
|
||||||
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/zap v1.21.0
|
go.uber.org/zap v1.21.0
|
||||||
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
|
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.99.0 // indirect
|
cloud.google.com/go v0.100.2 // indirect
|
||||||
|
cloud.google.com/go/compute v1.6.1 // indirect
|
||||||
|
cloud.google.com/go/iam v0.3.0 // indirect
|
||||||
cloud.google.com/go/storage v1.14.0 // indirect
|
cloud.google.com/go/storage v1.14.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
|
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
@@ -108,7 +115,6 @@ require (
|
|||||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
||||||
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
|
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
|
||||||
github.com/containerd/typeurl v1.0.2 // indirect
|
github.com/containerd/typeurl v1.0.2 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
|
||||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
@@ -124,6 +130,7 @@ require (
|
|||||||
github.com/emirpasic/gods v1.12.0 // indirect
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-errors/errors v1.0.1 // indirect
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.0 // indirect
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
@@ -146,7 +153,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/google/go-cmp v0.5.8 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/gosuri/uitable v0.0.4 // indirect
|
github.com/gosuri/uitable v0.0.4 // indirect
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||||
@@ -157,6 +164,7 @@ require (
|
|||||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
|
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
@@ -175,11 +183,9 @@ require (
|
|||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/liamg/iamgo v0.0.6 // indirect
|
github.com/liamg/iamgo v0.0.6 // indirect
|
||||||
github.com/liamg/jfather v0.0.7 // indirect
|
github.com/liamg/jfather v0.0.7 // indirect
|
||||||
github.com/liamg/memoryfs v1.4.2
|
|
||||||
github.com/liamg/tml v0.6.0
|
|
||||||
github.com/lib/pq v1.10.4 // indirect
|
github.com/lib/pq v1.10.4 // indirect
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
@@ -188,6 +194,7 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/buildkit v0.10.3
|
github.com/moby/buildkit v0.10.3
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
@@ -208,6 +215,7 @@ require (
|
|||||||
github.com/opencontainers/runtime-spec v1.0.3-0.20220311020903-6969a0a09ab1 // indirect
|
github.com/opencontainers/runtime-spec v1.0.3-0.20220311020903-6969a0a09ab1 // indirect
|
||||||
github.com/opencontainers/selinux v1.10.1 // indirect
|
github.com/opencontainers/selinux v1.10.1 // indirect
|
||||||
github.com/owenrumney/squealer v1.0.1-0.20220510063705-c0be93f0edea // indirect
|
github.com/owenrumney/squealer v1.0.1-0.20220510063705-c0be93f0edea // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -220,16 +228,16 @@ require (
|
|||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/rubenv/sql-migrate v1.1.1 // indirect
|
github.com/rubenv/sql-migrate v1.1.1 // indirect
|
||||||
github.com/russross/blackfriday v1.6.0 // indirect
|
github.com/russross/blackfriday v1.6.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spdx/tools-golang v0.3.0
|
github.com/spdx/tools-golang v0.3.0
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/afero v1.8.2 // indirect
|
||||||
github.com/spf13/cobra v1.4.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.4.0 // indirect
|
github.com/stretchr/objx v0.4.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.4.0 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.4.4 // indirect
|
github.com/vektah/gqlparser/v2 v2.4.4 // indirect
|
||||||
@@ -237,12 +245,10 @@ require (
|
|||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
|
||||||
github.com/yashtewari/glob-intersection v0.1.0 // indirect
|
github.com/yashtewari/glob-intersection v0.1.0 // indirect
|
||||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
|
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
|
||||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||||
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
|
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6
|
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
@@ -250,20 +256,21 @@ require (
|
|||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
|
||||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
||||||
google.golang.org/api v0.62.0 // indirect
|
google.golang.org/api v0.81.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
|
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
|
||||||
google.golang.org/grpc v1.47.0 // indirect
|
google.golang.org/grpc v1.47.0 // indirect
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gotest.tools v2.2.0+incompatible
|
gotest.tools v2.2.0+incompatible
|
||||||
|
|||||||
107
go.sum
107
go.sum
@@ -5,6 +5,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
@@ -28,18 +29,26 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
|
|||||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
|
||||||
cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
|
|
||||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||||
|
cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
|
||||||
|
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
||||||
|
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
||||||
|
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
||||||
|
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||||
|
cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=
|
||||||
|
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
|
||||||
|
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
@@ -454,7 +463,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
|
|||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@@ -565,6 +573,7 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
|
|||||||
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
|
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
|
||||||
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
|
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
@@ -801,11 +810,15 @@ github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1
|
|||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
||||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||||
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||||
@@ -866,6 +879,7 @@ github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
|||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4=
|
github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4=
|
||||||
github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
||||||
@@ -957,8 +971,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
@@ -991,8 +1005,9 @@ github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q
|
|||||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
@@ -1074,6 +1089,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
@@ -1209,6 +1226,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
|
|||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||||
@@ -1221,6 +1240,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
@@ -1278,6 +1298,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
|||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY=
|
github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY=
|
||||||
github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
|
github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
|
||||||
@@ -1285,13 +1306,12 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
|||||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||||
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||||
github.com/samber/lo v1.21.0 h1:FSby8pJQtX4KmyddTCCGhc3JvnnIVrDA+NW37rG+7G8=
|
github.com/samber/lo v1.24.0 h1:8BtUIUpAK2UfLv4/yI+1+1ux8brGwjhTpSndNWjRsjs=
|
||||||
github.com/samber/lo v1.21.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
github.com/samber/lo v1.24.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
|
||||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
@@ -1331,10 +1351,12 @@ github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAP
|
|||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
|
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||||
|
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
@@ -1344,6 +1366,7 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6
|
|||||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
@@ -1355,6 +1378,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
||||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||||
@@ -1375,6 +1399,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
|
||||||
|
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
@@ -1400,8 +1426,6 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
|||||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
|
|
||||||
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
|
||||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
||||||
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.4 h1:rh9hwZ5Jx9cCq88zXz2YHKmuQBuwY1JErHU8GywFdwE=
|
github.com/vektah/gqlparser/v2 v2.4.4 h1:rh9hwZ5Jx9cCq88zXz2YHKmuQBuwY1JErHU8GywFdwE=
|
||||||
@@ -1434,8 +1458,6 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6Ut
|
|||||||
github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
|
github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
|
||||||
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
|
||||||
github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
|
github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
|
||||||
github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
|
github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -1565,6 +1587,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
@@ -1677,6 +1700,11 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -1695,8 +1723,11 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
|
|||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -1708,6 +1739,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1829,11 +1861,15 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
|
||||||
@@ -1943,8 +1979,10 @@ golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
@@ -1977,8 +2015,15 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
|
|||||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||||
google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc=
|
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
||||||
|
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
||||||
|
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||||
|
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||||
|
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||||
|
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||||
|
google.golang.org/api v0.81.0 h1:o8WF5AvfidafWbFjsRyupxyEQJNUWxLZJCK5NXrxZZ8=
|
||||||
|
google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -2054,11 +2099,24 @@ google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEc
|
|||||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
|
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
|
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
|
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
|
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||||
|
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
|
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o=
|
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o=
|
||||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
@@ -2093,7 +2151,10 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
|
|||||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
|
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
|
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||||
@@ -2136,6 +2197,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
|||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||||
|
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build integration
|
//go:build integration
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
@@ -7,7 +6,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,10 +17,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
testcontainers "github.com/testcontainers/testcontainers-go"
|
testcontainers "github.com/testcontainers/testcontainers-go"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/clock"
|
"github.com/aquasecurity/trivy/pkg/clock"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -242,14 +238,14 @@ func TestClientServer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app, addr, cacheDir := setup(t, setupOptions{})
|
addr, cacheDir := setup(t, setupOptions{})
|
||||||
|
|
||||||
for _, c := range tests {
|
for _, c := range tests {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
|
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
|
||||||
|
|
||||||
// Run Trivy client
|
//
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
compareReports(t, c.golden, outputFile)
|
compareReports(t, c.golden, outputFile)
|
||||||
@@ -340,7 +336,7 @@ func TestClientServerWithFormat(t *testing.T) {
|
|||||||
report.CustomTemplateFuncMap = map[string]interface{}{}
|
report.CustomTemplateFuncMap = map[string]interface{}{}
|
||||||
})
|
})
|
||||||
|
|
||||||
app, addr, cacheDir := setup(t, setupOptions{})
|
addr, cacheDir := setup(t, setupOptions{})
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -349,7 +345,7 @@ func TestClientServerWithFormat(t *testing.T) {
|
|||||||
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, tt.golden)
|
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, tt.golden)
|
||||||
|
|
||||||
// Run Trivy client
|
// Run Trivy client
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
want, err := os.ReadFile(tt.golden)
|
want, err := os.ReadFile(tt.golden)
|
||||||
@@ -386,13 +382,13 @@ func TestClientServerWithCycloneDX(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app, addr, cacheDir := setup(t, setupOptions{})
|
addr, cacheDir := setup(t, setupOptions{})
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, "")
|
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, "")
|
||||||
|
|
||||||
// Run Trivy client
|
// Run Trivy client
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
f, err := os.Open(outputFile)
|
f, err := os.Open(outputFile)
|
||||||
@@ -450,7 +446,7 @@ func TestClientServerWithToken(t *testing.T) {
|
|||||||
|
|
||||||
serverToken := "token"
|
serverToken := "token"
|
||||||
serverTokenHeader := "Trivy-Token"
|
serverTokenHeader := "Trivy-Token"
|
||||||
app, addr, cacheDir := setup(t, setupOptions{
|
addr, cacheDir := setup(t, setupOptions{
|
||||||
token: serverToken,
|
token: serverToken,
|
||||||
tokenHeader: serverTokenHeader,
|
tokenHeader: serverTokenHeader,
|
||||||
})
|
})
|
||||||
@@ -460,16 +456,14 @@ func TestClientServerWithToken(t *testing.T) {
|
|||||||
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
|
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
|
||||||
|
|
||||||
// Run Trivy client
|
// Run Trivy client
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
|
|
||||||
if c.wantErr != "" {
|
if c.wantErr != "" {
|
||||||
require.NotNil(t, err, c.name)
|
require.Error(t, err, c.name)
|
||||||
assert.Contains(t, err.Error(), c.wantErr, c.name)
|
assert.Contains(t, err.Error(), c.wantErr, c.name)
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
assert.NoError(t, err, c.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err, c.name)
|
||||||
compareReports(t, c.golden, outputFile)
|
compareReports(t, c.golden, outputFile)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -481,7 +475,7 @@ func TestClientServerWithRedis(t *testing.T) {
|
|||||||
redisC, addr := setupRedis(t, ctx)
|
redisC, addr := setupRedis(t, ctx)
|
||||||
|
|
||||||
// Set up Trivy server
|
// Set up Trivy server
|
||||||
app, addr, cacheDir := setup(t, setupOptions{cacheBackend: addr})
|
addr, cacheDir := setup(t, setupOptions{cacheBackend: addr})
|
||||||
t.Cleanup(func() { os.RemoveAll(cacheDir) })
|
t.Cleanup(func() { os.RemoveAll(cacheDir) })
|
||||||
|
|
||||||
// Test parameters
|
// Test parameters
|
||||||
@@ -494,7 +488,7 @@ func TestClientServerWithRedis(t *testing.T) {
|
|||||||
osArgs, outputFile := setupClient(t, testArgs, addr, cacheDir, golden)
|
osArgs, outputFile := setupClient(t, testArgs, addr, cacheDir, golden)
|
||||||
|
|
||||||
// Run Trivy client
|
// Run Trivy client
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
compareReports(t, golden, outputFile)
|
compareReports(t, golden, outputFile)
|
||||||
@@ -507,8 +501,8 @@ func TestClientServerWithRedis(t *testing.T) {
|
|||||||
osArgs, _ := setupClient(t, testArgs, addr, cacheDir, golden)
|
osArgs, _ := setupClient(t, testArgs, addr, cacheDir, golden)
|
||||||
|
|
||||||
// Run Trivy client
|
// Run Trivy client
|
||||||
err := app.Run(osArgs)
|
err := execute(osArgs)
|
||||||
require.NotNil(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "connect: connection refused")
|
assert.Contains(t, err.Error(), "connect: connection refused")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -519,9 +513,8 @@ type setupOptions struct {
|
|||||||
cacheBackend string
|
cacheBackend string
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T, options setupOptions) (*cli.App, string, string) {
|
func setup(t *testing.T, options setupOptions) (string, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
version := "dev"
|
|
||||||
|
|
||||||
// Set up testing DB
|
// Set up testing DB
|
||||||
cacheDir := initDB(t)
|
cacheDir := initDB(t)
|
||||||
@@ -534,28 +527,21 @@ func setup(t *testing.T, options setupOptions) (*cli.App, string, string) {
|
|||||||
addr := fmt.Sprintf("localhost:%d", port)
|
addr := fmt.Sprintf("localhost:%d", port)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Setup CLI App
|
|
||||||
app := commands.NewApp(version)
|
|
||||||
app.Writer = io.Discard
|
|
||||||
osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend)
|
osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend)
|
||||||
|
|
||||||
// Run Trivy server
|
// Run Trivy server
|
||||||
app.Run(osArgs)
|
require.NoError(t, execute(osArgs))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
err = waitPort(ctx, addr)
|
err = waitPort(ctx, addr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Setup CLI App
|
return addr, cacheDir
|
||||||
app := commands.NewApp(version)
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
return app, addr, cacheDir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string {
|
func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string {
|
||||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "server", "--skip-update", "--listen", addr}
|
osArgs := []string{"--cache-dir", cacheDir, "server", "--skip-update", "--listen", addr}
|
||||||
if token != "" {
|
if token != "" {
|
||||||
osArgs = append(osArgs, []string{"--token", token, "--token-header", tokenHeader}...)
|
osArgs = append(osArgs, []string{"--token", token, "--token-header", tokenHeader}...)
|
||||||
}
|
}
|
||||||
@@ -573,7 +559,7 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden st
|
|||||||
c.RemoteAddrOption = "--server"
|
c.RemoteAddrOption = "--server"
|
||||||
}
|
}
|
||||||
t.Helper()
|
t.Helper()
|
||||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
|
osArgs := []string{"--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
|
||||||
|
|
||||||
if c.Format != "" {
|
if c.Format != "" {
|
||||||
osArgs = append(osArgs, "--format", c.Format)
|
osArgs = append(osArgs, "--format", c.Format)
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDockerEngine(t *testing.T) {
|
func TestDockerEngine(t *testing.T) {
|
||||||
@@ -233,16 +231,14 @@ func TestDockerEngine(t *testing.T) {
|
|||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
output := filepath.Join(tmpDir, "result.json")
|
output := filepath.Join(tmpDir, "result.json")
|
||||||
|
|
||||||
// run trivy
|
osArgs := []string{"--cache-dir", cacheDir, "image",
|
||||||
app := commands.NewApp("dev")
|
|
||||||
trivyArgs := []string{"trivy", "--cache-dir", cacheDir, "image",
|
|
||||||
"--skip-update", "--format=json", "--output", output}
|
"--skip-update", "--format=json", "--output", output}
|
||||||
|
|
||||||
if tt.ignoreUnfixed {
|
if tt.ignoreUnfixed {
|
||||||
trivyArgs = append(trivyArgs, "--ignore-unfixed")
|
osArgs = append(osArgs, "--ignore-unfixed")
|
||||||
}
|
}
|
||||||
if len(tt.severity) != 0 {
|
if len(tt.severity) != 0 {
|
||||||
trivyArgs = append(trivyArgs,
|
osArgs = append(osArgs,
|
||||||
[]string{"--severity", strings.Join(tt.severity, ",")}...,
|
[]string{"--severity", strings.Join(tt.severity, ",")}...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -252,11 +248,12 @@ func TestDockerEngine(t *testing.T) {
|
|||||||
assert.NoError(t, err, "failed to write .trivyignore")
|
assert.NoError(t, err, "failed to write .trivyignore")
|
||||||
defer os.Remove(trivyIgnore)
|
defer os.Remove(trivyIgnore)
|
||||||
}
|
}
|
||||||
trivyArgs = append(trivyArgs, tt.input)
|
osArgs = append(osArgs, tt.input)
|
||||||
|
|
||||||
err = app.Run(trivyArgs)
|
// Run Trivy
|
||||||
|
err = execute(osArgs)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.NotNil(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,13 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilesystem(t *testing.T) {
|
func TestFilesystem(t *testing.T) {
|
||||||
@@ -145,7 +143,7 @@ func TestFilesystem(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) {
|
||||||
osArgs := []string{
|
osArgs := []string{
|
||||||
"trivy", "--cache-dir", cacheDir, "fs", "--skip-db-update", "--skip-policy-update",
|
"-q", "--cache-dir", cacheDir, "fs", "--skip-db-update", "--skip-policy-update",
|
||||||
"--format", "json", "--offline-scan", "--security-checks", tt.args.securityChecks,
|
"--format", "json", "--offline-scan", "--security-checks", tt.args.securityChecks,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,12 +187,9 @@ func TestFilesystem(t *testing.T) {
|
|||||||
osArgs = append(osArgs, "--output", outputFile)
|
osArgs = append(osArgs, "--output", outputFile)
|
||||||
osArgs = append(osArgs, tt.args.input)
|
osArgs = append(osArgs, tt.args.input)
|
||||||
|
|
||||||
// Setup CLI App
|
|
||||||
app := commands.NewApp("dev")
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
// Run "trivy fs"
|
// Run "trivy fs"
|
||||||
assert.Nil(t, app.Run(osArgs))
|
err := execute(osArgs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Compare want and got
|
// Compare want and got
|
||||||
compareReports(t, tt.golden, outputFile)
|
compareReports(t, tt.golden, outputFile)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/commands"
|
||||||
"github.com/aquasecurity/trivy/pkg/dbtest"
|
"github.com/aquasecurity/trivy/pkg/dbtest"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
@@ -120,6 +122,16 @@ func readReport(t *testing.T, filePath string) types.Report {
|
|||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execute(osArgs []string) error {
|
||||||
|
// Setup CLI App
|
||||||
|
app := commands.NewApp("dev")
|
||||||
|
app.SetOut(io.Discard)
|
||||||
|
|
||||||
|
// Run Trivy
|
||||||
|
app.SetArgs(osArgs)
|
||||||
|
return app.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
func compareReports(t *testing.T, wantFile, gotFile string) {
|
func compareReports(t *testing.T, wantFile, gotFile string) {
|
||||||
want := readReport(t, wantFile)
|
want := readReport(t, wantFile)
|
||||||
got := readReport(t, gotFile)
|
got := readReport(t, gotFile)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/module"
|
"github.com/aquasecurity/trivy/pkg/module"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
)
|
)
|
||||||
@@ -48,13 +46,9 @@ func TestModule(t *testing.T) {
|
|||||||
filepath.Join(moduleDir, "spring4shell.wasm"))
|
filepath.Join(moduleDir, "spring4shell.wasm"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Setup CLI App
|
|
||||||
app := commands.NewApp("dev")
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--ignore-unfixed", "--format", "json",
|
osArgs := []string{"--cache-dir", cacheDir, "image", "--ignore-unfixed", "--format", "json",
|
||||||
"--skip-update", "--offline-scan", "--input", tt.input}
|
"--skip-update", "--offline-scan", "--input", tt.input}
|
||||||
|
|
||||||
// Set up the output file
|
// Set up the output file
|
||||||
@@ -66,7 +60,8 @@ func TestModule(t *testing.T) {
|
|||||||
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
||||||
|
|
||||||
// Run Trivy
|
// Run Trivy
|
||||||
assert.Nil(t, app.Run(osArgs))
|
err = execute(osArgs)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Compare want and got
|
// Compare want and got
|
||||||
compareReports(t, tt.golden, outputFile)
|
compareReports(t, tt.golden, outputFile)
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
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/commands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -235,15 +233,11 @@ func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup CLI App
|
osArgs := []string{"-q", "--cache-dir", cacheDir, "image", "--format", "json", "--skip-update",
|
||||||
app := commands.NewApp("dev")
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--format", "json", "--skip-update",
|
|
||||||
"--output", outputFile, imageRef.Name()}
|
"--output", outputFile, imageRef.Name()}
|
||||||
|
|
||||||
// Run Trivy
|
// Run Trivy
|
||||||
if err := app.Run(osArgs); err != nil {
|
if err := execute(osArgs); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return outputFile, nil
|
return outputFile, nil
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
//go:build integration
|
//go:build integration
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -12,8 +10,6 @@ import (
|
|||||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCycloneDX(t *testing.T) {
|
func TestCycloneDX(t *testing.T) {
|
||||||
@@ -53,7 +49,7 @@ func TestCycloneDX(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) {
|
||||||
osArgs := []string{
|
osArgs := []string{
|
||||||
"trivy", "--cache-dir", cacheDir, "sbom", "--skip-db-update", "--format", tt.args.format,
|
"--cache-dir", cacheDir, "sbom", "-q", "--skip-db-update", "--format", tt.args.format,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the output file
|
// Setup the output file
|
||||||
@@ -65,12 +61,9 @@ func TestCycloneDX(t *testing.T) {
|
|||||||
osArgs = append(osArgs, "--output", outputFile)
|
osArgs = append(osArgs, "--output", outputFile)
|
||||||
osArgs = append(osArgs, tt.args.input)
|
osArgs = append(osArgs, tt.args.input)
|
||||||
|
|
||||||
// Setup CLI App
|
|
||||||
app := commands.NewApp("dev")
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
// Run "trivy sbom"
|
// Run "trivy sbom"
|
||||||
assert.Nil(t, app.Run(osArgs))
|
err := execute(osArgs)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Compare want and got
|
// Compare want and got
|
||||||
want := decodeCycloneDX(t, tt.golden)
|
want := decodeCycloneDX(t, tt.golden)
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
//go:build integration
|
//go:build integration
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTar(t *testing.T) {
|
func TestTar(t *testing.T) {
|
||||||
@@ -264,13 +261,9 @@ func TestTar(t *testing.T) {
|
|||||||
// Set a temp dir so that modules will not be loaded
|
// Set a temp dir so that modules will not be loaded
|
||||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||||
|
|
||||||
// Setup CLI App
|
|
||||||
app := commands.NewApp("dev")
|
|
||||||
app.Writer = io.Discard
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--format", tt.testArgs.Format, "--skip-update"}
|
osArgs := []string{"--cache-dir", cacheDir, "image", "-q", "--format", tt.testArgs.Format, "--skip-update"}
|
||||||
|
|
||||||
if tt.testArgs.IgnoreUnfixed {
|
if tt.testArgs.IgnoreUnfixed {
|
||||||
osArgs = append(osArgs, "--ignore-unfixed")
|
osArgs = append(osArgs, "--ignore-unfixed")
|
||||||
@@ -310,7 +303,8 @@ func TestTar(t *testing.T) {
|
|||||||
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
||||||
|
|
||||||
// Run Trivy
|
// Run Trivy
|
||||||
assert.Nil(t, app.Run(osArgs))
|
err := execute(osArgs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Compare want and got
|
// Compare want and got
|
||||||
compareReports(t, tt.golden, outputFile)
|
compareReports(t, tt.golden, outputFile)
|
||||||
|
|||||||
1744
pkg/commands/app.go
1744
pkg/commands/app.go
File diff suppressed because it is too large
Load Diff
@@ -70,37 +70,36 @@ Vulnerability DB:
|
|||||||
name string
|
name string
|
||||||
arguments []string // 1st argument is path to trivy binaries
|
arguments []string // 1st argument is path to trivy binaries
|
||||||
want string
|
want string
|
||||||
wantErr string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy path. '-v' flag is used",
|
name: "happy path. '-v' flag is used",
|
||||||
arguments: []string{"trivy", "-v", "--cache-dir", "testdata"},
|
arguments: []string{"-v", "--cache-dir", "testdata"},
|
||||||
want: tableOutput,
|
want: tableOutput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy path. '-version' flag is used",
|
name: "happy path. '-version' flag is used",
|
||||||
arguments: []string{"trivy", "-version", "--cache-dir", "testdata"},
|
arguments: []string{"--version", "--cache-dir", "testdata"},
|
||||||
want: tableOutput,
|
want: tableOutput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy path. 'version' command is used",
|
name: "happy path. 'version' command is used",
|
||||||
arguments: []string{"trivy", "--cache-dir", "testdata", "version"},
|
arguments: []string{"--cache-dir", "testdata", "version"},
|
||||||
want: tableOutput,
|
want: tableOutput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy path. 'version', '--format json' flags are used",
|
name: "happy path. 'version', '--format json' flags are used",
|
||||||
arguments: []string{"trivy", "--cache-dir", "testdata", "version", "--format", "json"},
|
arguments: []string{"--cache-dir", "testdata", "version", "--format", "json"},
|
||||||
want: jsonOutput,
|
want: jsonOutput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sad path. '-v', '--format json' flags are used",
|
name: "happy path. '-v', '--format json' flags are used",
|
||||||
arguments: []string{"trivy", "-v", "--format", "json"},
|
arguments: []string{"--cache-dir", "testdata", "-v", "--format", "json"},
|
||||||
wantErr: "flag provided but not defined: -format",
|
want: jsonOutput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sad path. '-version', '--format json' flags are used",
|
name: "happy path. '--version', '--format json' flags are used",
|
||||||
arguments: []string{"trivy", "-version", "--format", "json"},
|
arguments: []string{"--cache-dir", "testdata", "--version", "--format", "json"},
|
||||||
wantErr: "flag provided but not defined: -format",
|
want: jsonOutput,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,24 +107,12 @@ Vulnerability DB:
|
|||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
got := new(bytes.Buffer)
|
got := new(bytes.Buffer)
|
||||||
app := NewApp("test")
|
app := NewApp("test")
|
||||||
app.Writer = got
|
SetOut(got)
|
||||||
|
app.SetArgs(test.arguments)
|
||||||
|
|
||||||
err := app.Run(test.arguments)
|
err := app.Execute()
|
||||||
if test.wantErr != "" {
|
require.NoError(t, err)
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), test.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(t, test.want, got.String())
|
assert.Equal(t, test.want, got.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCommands(t *testing.T) {
|
|
||||||
NewApp("test")
|
|
||||||
NewClientCommand()
|
|
||||||
NewFilesystemCommand()
|
|
||||||
NewImageCommand()
|
|
||||||
NewRepositoryCommand()
|
|
||||||
NewServerCommand()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigRun runs scan on config files
|
|
||||||
func ConfigRun(ctx *cli.Context) error {
|
|
||||||
opt, err := InitOption(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("option error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable OS and language analyzers
|
|
||||||
opt.DisabledAnalyzers = append(analyzer.TypeOSes, analyzer.TypeLanguages...)
|
|
||||||
|
|
||||||
// Scan only config files
|
|
||||||
opt.VulnType = nil
|
|
||||||
opt.SecurityChecks = []string{types.SecurityCheckConfig}
|
|
||||||
|
|
||||||
// Run filesystem command internally
|
|
||||||
return run(ctx.Context, opt, TargetFilesystem)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
|
|
||||||
func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
|
||||||
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
|
||||||
if err != nil {
|
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
|
||||||
}
|
|
||||||
return s, cleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
|
|
||||||
func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
|
||||||
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
|
||||||
if err != nil {
|
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
|
||||||
}
|
|
||||||
return s, cleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilesystemRun runs scan on filesystem for language-specific dependencies and config files
|
|
||||||
func FilesystemRun(ctx *cli.Context) error {
|
|
||||||
return Run(ctx, TargetFilesystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootfsRun runs scan on rootfs.
|
|
||||||
func RootfsRun(ctx *cli.Context) error {
|
|
||||||
return Run(ctx, TargetRootfs)
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option holds the artifact options
|
|
||||||
type Option struct {
|
|
||||||
option.GlobalOption
|
|
||||||
option.ArtifactOption
|
|
||||||
option.DBOption
|
|
||||||
option.ImageOption
|
|
||||||
option.ReportOption
|
|
||||||
option.CacheOption
|
|
||||||
option.ConfigOption
|
|
||||||
option.RemoteOption
|
|
||||||
option.SbomOption
|
|
||||||
option.SecretOption
|
|
||||||
option.KubernetesOption
|
|
||||||
option.OtherOption
|
|
||||||
|
|
||||||
// We don't want to allow disabled analyzers to be passed by users,
|
|
||||||
// but it differs depending on scanning modes.
|
|
||||||
DisabledAnalyzers []analyzer.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOption is the factory method to return options
|
|
||||||
func NewOption(c *cli.Context) (Option, error) {
|
|
||||||
gc, err := option.NewGlobalOption(c)
|
|
||||||
if err != nil {
|
|
||||||
return Option{}, xerrors.Errorf("failed to initialize global options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Option{
|
|
||||||
GlobalOption: gc,
|
|
||||||
ArtifactOption: option.NewArtifactOption(c),
|
|
||||||
DBOption: option.NewDBOption(c),
|
|
||||||
ImageOption: option.NewImageOption(c),
|
|
||||||
ReportOption: option.NewReportOption(c),
|
|
||||||
CacheOption: option.NewCacheOption(c),
|
|
||||||
ConfigOption: option.NewConfigOption(c),
|
|
||||||
RemoteOption: option.NewRemoteOption(c),
|
|
||||||
SbomOption: option.NewSbomOption(c),
|
|
||||||
SecretOption: option.NewSecretOption(c),
|
|
||||||
KubernetesOption: option.NewKubernetesOption(c),
|
|
||||||
OtherOption: option.NewOtherOption(c),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the artifact options
|
|
||||||
func (c *Option) Init() error {
|
|
||||||
if err := c.initPreScanOptions(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// --clear-cache, --download-db-only and --reset don't conduct the scan
|
|
||||||
if c.skipScan() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Option) initPreScanOptions() error {
|
|
||||||
if err := c.ReportOption.Init(c.Context.App.Writer, c.Logger); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.DBOption.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.CacheOption.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.SbomOption.Init(c.Context, c.Logger); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.RemoteOption.Init(c.Logger)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Option) skipScan() bool {
|
|
||||||
if c.ClearCache || c.DownloadDBOnly || c.Reset {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zaptest/observer"
|
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOption_Init(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
logs []string
|
|
||||||
want Option
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"--severity", "CRITICAL", "--vuln-type", "os", "--quiet", "alpine:3.10"},
|
|
||||||
want: Option{
|
|
||||||
GlobalOption: option.GlobalOption{
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.10",
|
|
||||||
},
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Output: os.Stdout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config scanning",
|
|
||||||
args: []string{"--severity", "CRITICAL", "--security-checks", "config", "--quiet", "alpine:3.10"},
|
|
||||||
want: Option{
|
|
||||||
GlobalOption: option.GlobalOption{
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.10",
|
|
||||||
},
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckConfig},
|
|
||||||
Output: os.Stdout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with token and token header",
|
|
||||||
args: []string{"--server", "http://localhost:8080", "--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.11",
|
|
||||||
},
|
|
||||||
RemoteOption: option.RemoteOption{
|
|
||||||
RemoteAddr: "http://localhost:8080",
|
|
||||||
CustomHeaders: http.Header{
|
|
||||||
"X-Trivy-Token": []string{"secret"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: token and token header without server",
|
|
||||||
args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
|
|
||||||
logs: []string{
|
|
||||||
`"--token" can be used only with "--server"`,
|
|
||||||
},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.11",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with good custom headers",
|
|
||||||
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foo:bar", "alpine:3.11"},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.11",
|
|
||||||
},
|
|
||||||
RemoteOption: option.RemoteOption{
|
|
||||||
RemoteAddr: "http://localhost:8080",
|
|
||||||
CustomHeaders: http.Header{
|
|
||||||
"Foo": []string{"bar"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with bad custom headers",
|
|
||||||
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foobaz", "alpine:3.11"},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.11",
|
|
||||||
},
|
|
||||||
RemoteOption: option.RemoteOption{RemoteAddr: "http://localhost:8080", CustomHeaders: http.Header{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path: reset",
|
|
||||||
args: []string{"--reset"},
|
|
||||||
want: Option{
|
|
||||||
DBOption: option.DBOption{
|
|
||||||
Reset: true,
|
|
||||||
},
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with an unknown severity",
|
|
||||||
args: []string{"--severity", "CRITICAL,INVALID", "centos:7"},
|
|
||||||
logs: []string{
|
|
||||||
"unknown severity option: unknown severity: INVALID",
|
|
||||||
},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "centos:7",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --template enabled without --format",
|
|
||||||
args: []string{"--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
|
|
||||||
},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --template and --format json",
|
|
||||||
args: []string{"--format", "json", "--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
|
|
||||||
},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
Format: "json",
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "json and list all packages",
|
|
||||||
args: []string{"--format", "json", "--list-all-pkgs", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Format: "json",
|
|
||||||
ListAllPkgs: true,
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --format template without --template",
|
|
||||||
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
|
|
||||||
},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityMedium},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Format: "template",
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "github enables list-all-pkgs",
|
|
||||||
args: []string{"--format", "github", "alpine:3.15"},
|
|
||||||
want: Option{
|
|
||||||
ReportOption: option.ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
Output: os.Stdout,
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Format: report.FormatGitHub,
|
|
||||||
ListAllPkgs: true,
|
|
||||||
},
|
|
||||||
ArtifactOption: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.15",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "sad: skip and download db",
|
|
||||||
args: []string{"--skip-db-update", "--download-db-only", "alpine:3.10"},
|
|
||||||
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sad: multiple image names",
|
|
||||||
args: []string{"centos:7", "alpine:3.10"},
|
|
||||||
logs: []string{
|
|
||||||
"multiple targets cannot be specified",
|
|
||||||
},
|
|
||||||
wantErr: "arguments error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
core, obs := observer.New(zap.InfoLevel)
|
|
||||||
logger := zap.New(core)
|
|
||||||
|
|
||||||
app := cli.NewApp()
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("quiet", false, "")
|
|
||||||
set.Bool("no-progress", false, "")
|
|
||||||
set.Bool("reset", false, "")
|
|
||||||
set.Bool("skip-db-update", false, "")
|
|
||||||
set.Bool("download-db-only", false, "")
|
|
||||||
set.Bool("list-all-pkgs", false, "")
|
|
||||||
set.String("severity", "CRITICAL", "")
|
|
||||||
set.String("vuln-type", "os,library", "")
|
|
||||||
set.String("security-checks", "vuln", "")
|
|
||||||
set.String("template", "", "")
|
|
||||||
set.String("format", "", "")
|
|
||||||
set.String("server", "", "")
|
|
||||||
set.String("token", "", "")
|
|
||||||
set.String("token-header", option.DefaultTokenHeader, "")
|
|
||||||
set.Var(&cli.StringSlice{}, "custom-headers", "")
|
|
||||||
|
|
||||||
ctx := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
c, err := NewOption(ctx)
|
|
||||||
require.NoError(t, err, err)
|
|
||||||
|
|
||||||
c.GlobalOption.Logger = logger.Sugar()
|
|
||||||
err = c.Init()
|
|
||||||
|
|
||||||
// tests log messages
|
|
||||||
var gotMessages []string
|
|
||||||
for _, entry := range obs.AllUntimed() {
|
|
||||||
gotMessages = append(gotMessages, entry.Message)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tt.logs, gotMessages, tt.name)
|
|
||||||
|
|
||||||
// test the error
|
|
||||||
switch {
|
|
||||||
case tt.wantErr != "":
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
assert.NoError(t, err, tt.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.want.GlobalOption.Context = ctx
|
|
||||||
tt.want.GlobalOption.Logger = logger.Sugar()
|
|
||||||
assert.Equal(t, tt.want, c, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
|
|
||||||
func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
|
||||||
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
|
||||||
if err != nil {
|
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
|
||||||
}
|
|
||||||
return s, cleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepositoryRun runs scan on repository
|
|
||||||
func RepositoryRun(ctx *cli.Context) error {
|
|
||||||
return Run(ctx, TargetRepository)
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@@ -18,6 +17,7 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
|
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
|
||||||
"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"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/module"
|
"github.com/aquasecurity/trivy/pkg/module"
|
||||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||||
@@ -65,19 +65,19 @@ type ScannerConfig struct {
|
|||||||
|
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
// ScanImage scans an image
|
// ScanImage scans an image
|
||||||
ScanImage(ctx context.Context, opt Option) (types.Report, error)
|
ScanImage(ctx context.Context, opts flag.Options) (types.Report, error)
|
||||||
// ScanFilesystem scans a filesystem
|
// ScanFilesystem scans a filesystem
|
||||||
ScanFilesystem(ctx context.Context, opt Option) (types.Report, error)
|
ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error)
|
||||||
// ScanRootfs scans rootfs
|
// ScanRootfs scans rootfs
|
||||||
ScanRootfs(ctx context.Context, opt Option) (types.Report, error)
|
ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error)
|
||||||
// ScanRepository scans repository
|
// ScanRepository scans repository
|
||||||
ScanRepository(ctx context.Context, opt Option) (types.Report, error)
|
ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error)
|
||||||
// ScanSBOM scans SBOM
|
// ScanSBOM scans SBOM
|
||||||
ScanSBOM(ctx context.Context, opt Option) (types.Report, error)
|
ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error)
|
||||||
// Filter filter a report
|
// Filter filter a report
|
||||||
Filter(ctx context.Context, opt Option, report types.Report) (types.Report, error)
|
Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error)
|
||||||
// Report a writes a report
|
// Report a writes a report
|
||||||
Report(opt Option, report types.Report) error
|
Report(opts flag.Options, report types.Report) error
|
||||||
// Close closes runner
|
// Close closes runner
|
||||||
Close(ctx context.Context) error
|
Close(ctx context.Context) error
|
||||||
}
|
}
|
||||||
@@ -93,6 +93,7 @@ type runner struct {
|
|||||||
type runnerOption func(*runner)
|
type runnerOption func(*runner)
|
||||||
|
|
||||||
// WithCacheClient takes a custom cache implementation
|
// WithCacheClient takes a custom cache implementation
|
||||||
|
// It is useful when Trivy is imported as a library.
|
||||||
func WithCacheClient(c cache.Cache) runnerOption {
|
func WithCacheClient(c cache.Cache) runnerOption {
|
||||||
return func(r *runner) {
|
return func(r *runner) {
|
||||||
r.cache = c
|
r.cache = c
|
||||||
@@ -101,23 +102,23 @@ func WithCacheClient(c cache.Cache) runnerOption {
|
|||||||
|
|
||||||
// NewRunner initializes Runner that provides scanning functionalities.
|
// NewRunner initializes Runner that provides scanning functionalities.
|
||||||
// It is possible to return SkipScan and it must be handled by caller.
|
// It is possible to return SkipScan and it must be handled by caller.
|
||||||
func NewRunner(cliOption Option, opts ...runnerOption) (Runner, error) {
|
func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) {
|
||||||
r := &runner{}
|
r := &runner{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(r)
|
opt(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := log.InitLogger(cliOption.Debug, cliOption.Quiet)
|
if err := r.initCache(cliOptions); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("logger error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = r.initCache(cliOption); err != nil {
|
|
||||||
return nil, xerrors.Errorf("cache error: %w", err)
|
return nil, xerrors.Errorf("cache error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the vulnerability database if needed.
|
||||||
|
if err := r.initDB(cliOptions); err != nil {
|
||||||
|
return nil, xerrors.Errorf("DB error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize WASM modules
|
// Initialize WASM modules
|
||||||
m, err := module.NewManager(cliOption.Context.Context)
|
m, err := module.NewManager(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("WASM module error: %w", err)
|
return nil, xerrors.Errorf("WASM module error: %w", err)
|
||||||
}
|
}
|
||||||
@@ -146,46 +147,46 @@ func (r *runner) Close(ctx context.Context) error {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) ScanImage(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
// Disable the lock file scanning
|
// Disable the lock file scanning
|
||||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
opts.DisabledAnalyzers = analyzer.TypeLockfiles
|
||||||
|
|
||||||
var s InitializeScanner
|
var s InitializeScanner
|
||||||
switch {
|
switch {
|
||||||
case opt.Input != "" && opt.RemoteAddr == "":
|
case opts.Input != "" && opts.ServerAddr == "":
|
||||||
// Scan image tarball in standalone mode
|
// Scan image tarball in standalone mode
|
||||||
s = archiveStandaloneScanner
|
s = archiveStandaloneScanner
|
||||||
case opt.Input != "" && opt.RemoteAddr != "":
|
case opts.Input != "" && opts.ServerAddr != "":
|
||||||
// Scan image tarball in client/server mode
|
// Scan image tarball in client/server mode
|
||||||
s = archiveRemoteScanner
|
s = archiveRemoteScanner
|
||||||
case opt.Input == "" && opt.RemoteAddr == "":
|
case opts.Input == "" && opts.ServerAddr == "":
|
||||||
// Scan container image in standalone mode
|
// Scan container image in standalone mode
|
||||||
s = imageStandaloneScanner
|
s = imageStandaloneScanner
|
||||||
case opt.Input == "" && opt.RemoteAddr != "":
|
case opts.Input == "" && opts.ServerAddr != "":
|
||||||
// Scan container image in client/server mode
|
// Scan container image in client/server mode
|
||||||
s = imageRemoteScanner
|
s = imageRemoteScanner
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.scanArtifact(ctx, opt, s)
|
return r.scanArtifact(ctx, opts, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) ScanFilesystem(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
// Disable the individual package scanning
|
// Disable the individual package scanning
|
||||||
opt.DisabledAnalyzers = append(opt.DisabledAnalyzers, analyzer.TypeIndividualPkgs...)
|
opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeIndividualPkgs...)
|
||||||
|
|
||||||
return r.scanFS(ctx, opt)
|
return r.scanFS(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) ScanRootfs(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
// Disable the lock file scanning
|
// Disable the lock file scanning
|
||||||
opt.DisabledAnalyzers = append(opt.DisabledAnalyzers, analyzer.TypeLockfiles...)
|
opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeLockfiles...)
|
||||||
|
|
||||||
return r.scanFS(ctx, opt)
|
return r.scanFS(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) scanFS(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) scanFS(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
var s InitializeScanner
|
var s InitializeScanner
|
||||||
if opt.RemoteAddr == "" {
|
if opts.ServerAddr == "" {
|
||||||
// Scan filesystem in standalone mode
|
// Scan filesystem in standalone mode
|
||||||
s = filesystemStandaloneScanner
|
s = filesystemStandaloneScanner
|
||||||
} else {
|
} else {
|
||||||
@@ -193,26 +194,22 @@ func (r *runner) scanFS(ctx context.Context, opt Option) (types.Report, error) {
|
|||||||
s = filesystemRemoteScanner
|
s = filesystemRemoteScanner
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.scanArtifact(ctx, opt, s)
|
return r.scanArtifact(ctx, opts, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) ScanRepository(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
// Do not scan OS packages
|
// Do not scan OS packages
|
||||||
opt.VulnType = []string{types.VulnTypeLibrary}
|
opts.VulnType = []string{types.VulnTypeLibrary}
|
||||||
|
|
||||||
// Disable the OS analyzers and individual package analyzers
|
// Disable the OS analyzers and individual package analyzers
|
||||||
opt.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
|
opts.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
|
||||||
|
|
||||||
return r.scanArtifact(ctx, opt, repositoryStandaloneScanner)
|
return r.scanArtifact(ctx, opts, repositoryStandaloneScanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) ScanSBOM(ctx context.Context, opt Option) (types.Report, error) {
|
func (r *runner) ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error) {
|
||||||
// Scan vulnerabilities
|
|
||||||
opt.ReportOption.VulnType = []string{types.VulnTypeOS, types.VulnTypeLibrary}
|
|
||||||
opt.ReportOption.SecurityChecks = []string{types.SecurityCheckVulnerability}
|
|
||||||
|
|
||||||
var s InitializeScanner
|
var s InitializeScanner
|
||||||
if opt.RemoteAddr == "" {
|
if opts.ServerAddr == "" {
|
||||||
// Scan cycloneDX in standalone mode
|
// Scan cycloneDX in standalone mode
|
||||||
s = sbomStandaloneScanner
|
s = sbomStandaloneScanner
|
||||||
} else {
|
} else {
|
||||||
@@ -220,16 +217,12 @@ func (r *runner) ScanSBOM(ctx context.Context, opt Option) (types.Report, error)
|
|||||||
s = sbomRemoteScanner
|
s = sbomRemoteScanner
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.scanArtifact(ctx, opt, s)
|
return r.scanArtifact(ctx, opts, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) scanArtifact(ctx context.Context, opt Option, initializeScanner InitializeScanner) (types.Report, error) {
|
func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) {
|
||||||
// Update the vulnerability database if needed.
|
|
||||||
if err := r.initDB(opt); err != nil {
|
|
||||||
return types.Report{}, xerrors.Errorf("DB error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
report, err := scan(ctx, opt, initializeScanner, r.cache)
|
report, err := scan(ctx, opts, initializeScanner, r.cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Report{}, xerrors.Errorf("scan error: %w", err)
|
return types.Report{}, xerrors.Errorf("scan error: %w", err)
|
||||||
}
|
}
|
||||||
@@ -237,13 +230,13 @@ func (r *runner) scanArtifact(ctx context.Context, opt Option, initializeScanner
|
|||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Filter(ctx context.Context, opt Option, report types.Report) (types.Report, error) {
|
func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) {
|
||||||
results := report.Results
|
results := report.Results
|
||||||
|
|
||||||
// Filter results
|
// Filter results
|
||||||
for i := range results {
|
for i := range results {
|
||||||
vulns, misconfSummary, misconfs, secrets, err := result.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
|
vulns, misconfSummary, misconfs, secrets, err := result.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
|
||||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
opts.Severities, opts.IgnoreUnfixed, opts.IncludeNonFailures, opts.IgnoreFile, opts.IgnorePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
||||||
}
|
}
|
||||||
@@ -255,16 +248,16 @@ func (r *runner) Filter(ctx context.Context, opt Option, report types.Report) (t
|
|||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Report(opt Option, report types.Report) error {
|
func (r *runner) Report(opts flag.Options, report types.Report) error {
|
||||||
if err := pkgReport.Write(report, pkgReport.Option{
|
if err := pkgReport.Write(report, pkgReport.Option{
|
||||||
AppVersion: opt.GlobalOption.AppVersion,
|
AppVersion: opts.AppVersion,
|
||||||
Format: opt.Format,
|
Format: opts.Format,
|
||||||
Output: opt.Output,
|
Output: opts.Output,
|
||||||
Tree: opt.DependencyTree,
|
Tree: opts.DependencyTree,
|
||||||
Severities: opt.Severities,
|
Severities: opts.Severities,
|
||||||
OutputTemplate: opt.Template,
|
OutputTemplate: opts.Template,
|
||||||
IncludeNonFailures: opt.IncludeNonFailures,
|
IncludeNonFailures: opts.IncludeNonFailures,
|
||||||
Trace: opt.Trace,
|
Trace: opts.Trace,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
return xerrors.Errorf("unable to write results: %w", err)
|
||||||
}
|
}
|
||||||
@@ -272,23 +265,23 @@ func (r *runner) Report(opt Option, report types.Report) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) initDB(c Option) error {
|
func (r *runner) initDB(opts flag.Options) error {
|
||||||
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
|
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
|
||||||
if c.RemoteAddr != "" || !slices.Contains(c.SecurityChecks, types.SecurityCheckVulnerability) {
|
if opts.ServerAddr != "" || !slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// download the database file
|
// download the database file
|
||||||
noProgress := c.Quiet || c.NoProgress
|
noProgress := opts.Quiet || opts.NoProgress
|
||||||
if err := operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, noProgress, c.Insecure, c.SkipDBUpdate); err != nil {
|
if err := operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.Insecure, opts.SkipDBUpdate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.DownloadDBOnly {
|
if opts.DownloadDBOnly {
|
||||||
return SkipScan
|
return SkipScan
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Init(c.CacheDir); err != nil {
|
if err := db.Init(opts.CacheDir); err != nil {
|
||||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||||
}
|
}
|
||||||
r.dbOpen = true
|
r.dbOpen = true
|
||||||
@@ -296,58 +289,59 @@ func (r *runner) initDB(c Option) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) initCache(c Option) error {
|
func (r *runner) initCache(opts flag.Options) error {
|
||||||
// Skip initializing cache when custom cache is passed
|
// Skip initializing cache when custom cache is passed
|
||||||
if r.cache != nil {
|
if r.cache != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// client/server mode
|
// client/server mode
|
||||||
if c.RemoteAddr != "" {
|
if opts.ServerAddr != "" {
|
||||||
remoteCache := tcache.NewRemoteCache(c.RemoteAddr, c.CustomHeaders, c.Insecure)
|
remoteCache := tcache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure)
|
||||||
r.cache = tcache.NopCache(remoteCache)
|
r.cache = tcache.NopCache(remoteCache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// standalone mode
|
// standalone mode
|
||||||
utils.SetCacheDir(c.CacheDir)
|
utils.SetCacheDir(opts.CacheDir)
|
||||||
cache, err := operation.NewCache(c.CacheOption)
|
cacheClient, err := operation.NewCache(opts.CacheOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("unable to initialize the cache: %w", err)
|
return xerrors.Errorf("unable to initialize the cache: %w", err)
|
||||||
}
|
}
|
||||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||||
|
|
||||||
if c.Reset {
|
if opts.Reset {
|
||||||
defer cache.Close()
|
defer cacheClient.Close()
|
||||||
if err = cache.Reset(); err != nil {
|
if err = cacheClient.Reset(); err != nil {
|
||||||
return xerrors.Errorf("cache reset error: %w", err)
|
return xerrors.Errorf("cache reset error: %w", err)
|
||||||
}
|
}
|
||||||
return SkipScan
|
return SkipScan
|
||||||
}
|
}
|
||||||
if c.ClearCache {
|
if opts.ClearCache {
|
||||||
defer cache.Close()
|
defer cacheClient.Close()
|
||||||
if err = cache.ClearArtifacts(); err != nil {
|
if err = cacheClient.ClearArtifacts(); err != nil {
|
||||||
return xerrors.Errorf("cache clear error: %w", err)
|
return xerrors.Errorf("cache clear error: %w", err)
|
||||||
}
|
}
|
||||||
return SkipScan
|
return SkipScan
|
||||||
}
|
}
|
||||||
|
|
||||||
r.cache = cache
|
r.cache = cacheClient
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run performs artifact scanning
|
// Run performs artifact scanning
|
||||||
func Run(cliCtx *cli.Context, targetKind TargetKind) error {
|
//func Run(cliCtx *cli.Context, targetKind TargetKind) error {
|
||||||
opt, err := InitOption(cliCtx)
|
// opt, err := InitOption(cliCtx)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return xerrors.Errorf("InitOption: %w", err)
|
// return xerrors.Errorf("InitOption: %w", err)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// return run(cliCtx.Context, opt, targetKind)
|
||||||
|
//}
|
||||||
|
|
||||||
return run(cliCtx.Context, opt, targetKind)
|
// Run performs artifact scanning
|
||||||
}
|
func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
|
||||||
func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
|
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -356,7 +350,7 @@ func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
r, err := NewRunner(opt)
|
r, err := NewRunner(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, SkipScan) {
|
if errors.Is(err, SkipScan) {
|
||||||
return nil
|
return nil
|
||||||
@@ -368,121 +362,107 @@ func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
|
|||||||
var report types.Report
|
var report types.Report
|
||||||
switch targetKind {
|
switch targetKind {
|
||||||
case TargetContainerImage, TargetImageArchive:
|
case TargetContainerImage, TargetImageArchive:
|
||||||
if report, err = r.ScanImage(ctx, opt); err != nil {
|
if report, err = r.ScanImage(ctx, opts); err != nil {
|
||||||
return xerrors.Errorf("image scan error: %w", err)
|
return xerrors.Errorf("image scan error: %w", err)
|
||||||
}
|
}
|
||||||
case TargetFilesystem:
|
case TargetFilesystem:
|
||||||
if report, err = r.ScanFilesystem(ctx, opt); err != nil {
|
if report, err = r.ScanFilesystem(ctx, opts); err != nil {
|
||||||
return xerrors.Errorf("filesystem scan error: %w", err)
|
return xerrors.Errorf("filesystem scan error: %w", err)
|
||||||
}
|
}
|
||||||
case TargetRootfs:
|
case TargetRootfs:
|
||||||
if report, err = r.ScanRootfs(ctx, opt); err != nil {
|
if report, err = r.ScanRootfs(ctx, opts); err != nil {
|
||||||
return xerrors.Errorf("rootfs scan error: %w", err)
|
return xerrors.Errorf("rootfs scan error: %w", err)
|
||||||
}
|
}
|
||||||
case TargetRepository:
|
case TargetRepository:
|
||||||
if report, err = r.ScanRepository(ctx, opt); err != nil {
|
if report, err = r.ScanRepository(ctx, opts); err != nil {
|
||||||
return xerrors.Errorf("repository scan error: %w", err)
|
return xerrors.Errorf("repository scan error: %w", err)
|
||||||
}
|
}
|
||||||
case TargetSBOM:
|
case TargetSBOM:
|
||||||
if report, err = r.ScanSBOM(ctx, opt); err != nil {
|
if report, err = r.ScanSBOM(ctx, opts); err != nil {
|
||||||
return xerrors.Errorf("sbom scan error: %w", err)
|
return xerrors.Errorf("sbom scan error: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
report, err = r.Filter(ctx, opt, report)
|
report, err = r.Filter(ctx, opts, report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("filter error: %w", err)
|
return xerrors.Errorf("filter error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = r.Report(opt, report); err != nil {
|
if err = r.Report(opts, report); err != nil {
|
||||||
return xerrors.Errorf("report error: %w", err)
|
return xerrors.Errorf("report error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Exit(opt, report.Results.Failed())
|
Exit(opts, report.Results.Failed())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitOption(ctx *cli.Context) (Option, error) {
|
func disabledAnalyzers(opts flag.Options) []analyzer.Type {
|
||||||
opt, err := NewOption(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return Option{}, xerrors.Errorf("option error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize options
|
|
||||||
if err = opt.Init(); err != nil {
|
|
||||||
return Option{}, xerrors.Errorf("option initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return opt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func disabledAnalyzers(opt Option) []analyzer.Type {
|
|
||||||
// Specified analyzers to be disabled depending on scanning modes
|
// Specified analyzers to be disabled depending on scanning modes
|
||||||
// e.g. The 'image' subcommand should disable the lock file scanning.
|
// e.g. The 'image' subcommand should disable the lock file scanning.
|
||||||
analyzers := opt.DisabledAnalyzers
|
analyzers := opts.DisabledAnalyzers
|
||||||
|
|
||||||
// It doesn't analyze apk commands by default.
|
// It doesn't analyze apk commands by default.
|
||||||
if !opt.ScanRemovedPkgs {
|
if !opts.ScanRemovedPkgs {
|
||||||
analyzers = append(analyzers, analyzer.TypeApkCommand)
|
analyzers = append(analyzers, analyzer.TypeApkCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not analyze programming language packages when not running in 'library' mode
|
// Do not analyze programming language packages when not running in 'library' mode
|
||||||
if !slices.Contains(opt.VulnType, types.VulnTypeLibrary) {
|
if !slices.Contains(opts.VulnType, types.VulnTypeLibrary) {
|
||||||
analyzers = append(analyzers, analyzer.TypeLanguages...)
|
analyzers = append(analyzers, analyzer.TypeLanguages...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not perform secret scanning when it is not specified.
|
// Do not perform secret scanning when it is not specified.
|
||||||
if !slices.Contains(opt.SecurityChecks, types.SecurityCheckSecret) {
|
if !slices.Contains(opts.SecurityChecks, types.SecurityCheckSecret) {
|
||||||
analyzers = append(analyzers, analyzer.TypeSecret)
|
analyzers = append(analyzers, analyzer.TypeSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not perform misconfiguration scanning when it is not specified.
|
// Do not perform misconfiguration scanning when it is not specified.
|
||||||
if !slices.Contains(opt.SecurityChecks, types.SecurityCheckConfig) {
|
if !slices.Contains(opts.SecurityChecks, types.SecurityCheckConfig) {
|
||||||
analyzers = append(analyzers, analyzer.TypeConfigFiles...)
|
analyzers = append(analyzers, analyzer.TypeConfigFiles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return analyzers
|
return analyzers
|
||||||
}
|
}
|
||||||
|
|
||||||
func initScannerConfig(opt Option, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) {
|
func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) {
|
||||||
target := opt.Target
|
target := opts.Target
|
||||||
if opt.Input != "" {
|
if opts.Input != "" {
|
||||||
target = opt.Input
|
target = opts.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
scanOptions := types.ScanOptions{
|
scanOptions := types.ScanOptions{
|
||||||
VulnType: opt.VulnType,
|
VulnType: opts.VulnType,
|
||||||
SecurityChecks: opt.SecurityChecks,
|
SecurityChecks: opts.SecurityChecks,
|
||||||
ScanRemovedPackages: opt.ScanRemovedPkgs, // this is valid only for 'image' subcommand
|
ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand
|
||||||
ListAllPackages: opt.ListAllPkgs,
|
ListAllPackages: opts.ListAllPkgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(opt.SecurityChecks, types.SecurityCheckVulnerability) {
|
if slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) {
|
||||||
log.Logger.Info("Vulnerability scanning is enabled")
|
log.Logger.Info("Vulnerability scanning is enabled")
|
||||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScannerOption is filled only when config scanning is enabled.
|
// ScannerOption is filled only when config scanning is enabled.
|
||||||
var configScannerOptions config.ScannerOption
|
var configScannerOptions config.ScannerOption
|
||||||
if slices.Contains(opt.SecurityChecks, types.SecurityCheckConfig) {
|
if slices.Contains(opts.SecurityChecks, types.SecurityCheckConfig) {
|
||||||
log.Logger.Info("Misconfiguration scanning is enabled")
|
log.Logger.Info("Misconfiguration scanning is enabled")
|
||||||
configScannerOptions = config.ScannerOption{
|
configScannerOptions = config.ScannerOption{
|
||||||
Trace: opt.Trace,
|
Trace: opts.Trace,
|
||||||
Namespaces: append(opt.PolicyNamespaces, defaultPolicyNamespaces...),
|
Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...),
|
||||||
PolicyPaths: opt.PolicyPaths,
|
PolicyPaths: opts.PolicyPaths,
|
||||||
DataPaths: opt.DataPaths,
|
DataPaths: opts.DataPaths,
|
||||||
FilePatterns: opt.FilePatterns,
|
FilePatterns: opts.FilePatterns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not load config file for secret scanning
|
// Do not load config file for secret scanning
|
||||||
if slices.Contains(opt.SecurityChecks, types.SecurityCheckSecret) {
|
if slices.Contains(opts.SecurityChecks, types.SecurityCheckSecret) {
|
||||||
log.Logger.Info("Secret scanning is enabled")
|
log.Logger.Info("Secret scanning is enabled")
|
||||||
log.Logger.Info("If your scanning is slow, please try '--security-checks vuln' to disable secret scanning")
|
log.Logger.Info("If your scanning is slow, please try '--security-checks vuln' to disable secret scanning")
|
||||||
log.Logger.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/secret/scanning/#recommendation for faster secret detection", opt.AppVersion)
|
log.Logger.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/secret/scanning/#recommendation for faster secret detection", opts.AppVersion)
|
||||||
} else {
|
} else {
|
||||||
opt.SecretConfigPath = ""
|
opts.SecretConfigPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScannerConfig{
|
return ScannerConfig{
|
||||||
@@ -490,33 +470,33 @@ func initScannerConfig(opt Option, cacheClient cache.Cache) (ScannerConfig, type
|
|||||||
ArtifactCache: cacheClient,
|
ArtifactCache: cacheClient,
|
||||||
LocalArtifactCache: cacheClient,
|
LocalArtifactCache: cacheClient,
|
||||||
RemoteOption: client.ScannerOption{
|
RemoteOption: client.ScannerOption{
|
||||||
RemoteURL: opt.RemoteAddr,
|
RemoteURL: opts.ServerAddr,
|
||||||
CustomHeaders: opt.CustomHeaders,
|
CustomHeaders: opts.CustomHeaders,
|
||||||
Insecure: opt.Insecure,
|
Insecure: opts.Insecure,
|
||||||
},
|
},
|
||||||
ArtifactOption: artifact.Option{
|
ArtifactOption: artifact.Option{
|
||||||
DisabledAnalyzers: disabledAnalyzers(opt),
|
DisabledAnalyzers: disabledAnalyzers(opts),
|
||||||
SkipFiles: opt.SkipFiles,
|
SkipFiles: opts.SkipFiles,
|
||||||
SkipDirs: opt.SkipDirs,
|
SkipDirs: opts.SkipDirs,
|
||||||
InsecureSkipTLS: opt.Insecure,
|
InsecureSkipTLS: opts.Insecure,
|
||||||
Offline: opt.OfflineScan,
|
Offline: opts.OfflineScan,
|
||||||
NoProgress: opt.NoProgress || opt.Quiet,
|
NoProgress: opts.NoProgress || opts.Quiet,
|
||||||
|
|
||||||
// For misconfiguration scanning
|
// For misconfiguration scanning
|
||||||
MisconfScannerOption: configScannerOptions,
|
MisconfScannerOption: configScannerOptions,
|
||||||
|
|
||||||
// For secret scanning
|
// For secret scanning
|
||||||
SecretScannerOption: secret.ScannerOption{
|
SecretScannerOption: secret.ScannerOption{
|
||||||
ConfigPath: opt.SecretConfigPath,
|
ConfigPath: opts.SecretConfigPath,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, scanOptions, nil
|
}, scanOptions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner, cacheClient cache.Cache) (
|
func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) (
|
||||||
types.Report, error) {
|
types.Report, error) {
|
||||||
|
|
||||||
scannerConfig, scanOptions, err := initScannerConfig(opt, cacheClient)
|
scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Report{}, err
|
return types.Report{}, err
|
||||||
}
|
}
|
||||||
@@ -534,8 +514,8 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
|||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Exit(c Option, failedResults bool) {
|
func Exit(opts flag.Options, failedResults bool) {
|
||||||
if c.ExitCode != 0 && failedResults {
|
if opts.ExitCode != 0 && failedResults {
|
||||||
os.Exit(c.ExitCode)
|
os.Exit(opts.ExitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SBOMRun scans SBOM for vulnerabilities
|
|
||||||
func SBOMRun(ctx *cli.Context) error {
|
|
||||||
return Run(ctx, TargetSBOM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
|
||||||
s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
|
||||||
if err != nil {
|
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
|
|
||||||
}
|
|
||||||
return s, cleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
|
||||||
s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
|
||||||
if err != nil {
|
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
|
|
||||||
}
|
|
||||||
return s, cleanup, nil
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ package artifact
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||||
@@ -64,7 +63,47 @@ func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scan
|
|||||||
return s, func() {}, nil
|
return s, func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageRun runs scan on container image
|
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
|
||||||
func ImageRun(ctx *cli.Context) error {
|
func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
||||||
return Run(ctx, TargetContainerImage)
|
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||||
|
if err != nil {
|
||||||
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||||
|
}
|
||||||
|
return s, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
|
||||||
|
func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
||||||
|
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
||||||
|
if err != nil {
|
||||||
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||||
|
}
|
||||||
|
return s, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
|
||||||
|
func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
||||||
|
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||||
|
if err != nil {
|
||||||
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||||
|
}
|
||||||
|
return s, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sbomStandaloneScanner initializes a SBOM scanner in standalone mode
|
||||||
|
func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
||||||
|
s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||||
|
if err != nil {
|
||||||
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
|
||||||
|
}
|
||||||
|
return s, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sbomRemoteScanner initializes a SBOM scanner in client/server mode
|
||||||
|
func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
|
||||||
|
s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
||||||
|
if err != nil {
|
||||||
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
|
||||||
|
}
|
||||||
|
return s, cleanup, nil
|
||||||
}
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package module
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/module"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install installs a module
|
|
||||||
func Install(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("log initialization error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := c.Args().First()
|
|
||||||
if err := module.Install(c.Context, repo, c.Bool("quiet"), c.Bool("insecure")); err != nil {
|
|
||||||
return xerrors.Errorf("module installation error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uninstall uninstalls a module
|
|
||||||
func Uninstall(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("log initialization error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := c.Args().First()
|
|
||||||
if err := module.Uninstall(c.Context, repo); err != nil {
|
|
||||||
return xerrors.Errorf("module uninstall error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initLogger(ctx *cli.Context) error {
|
|
||||||
conf, err := option.NewGlobalOption(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("config error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
|
|
||||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/db"
|
"github.com/aquasecurity/trivy/pkg/db"
|
||||||
"github.com/aquasecurity/trivy/pkg/fanal/cache"
|
"github.com/aquasecurity/trivy/pkg/fanal/cache"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
@@ -31,7 +34,7 @@ type Cache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCache is the factory method for Cache
|
// NewCache is the factory method for Cache
|
||||||
func NewCache(c option.CacheOption) (Cache, error) {
|
func NewCache(c flag.CacheOptions) (Cache, error) {
|
||||||
if strings.HasPrefix(c.CacheBackend, "redis://") {
|
if strings.HasPrefix(c.CacheBackend, "redis://") {
|
||||||
log.Logger.Infof("Redis cache: %s", c.CacheBackendMasked())
|
log.Logger.Infof("Redis cache: %s", c.CacheBackendMasked())
|
||||||
options, err := redis.ParseURL(c.CacheBackend)
|
options, err := redis.ParseURL(c.CacheBackend)
|
||||||
@@ -39,7 +42,7 @@ func NewCache(c option.CacheOption) (Cache, error) {
|
|||||||
return Cache{}, err
|
return Cache{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.RedisOption{}) != c.RedisOption {
|
if !lo.IsEmpty(c.RedisOptions) {
|
||||||
caCert, cert, err := utils.GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey)
|
caCert, cert, err := utils.GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Cache{}, err
|
return Cache{}, err
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ArtifactOption holds the options for an artifact scanning
|
|
||||||
type ArtifactOption struct {
|
|
||||||
Input string
|
|
||||||
Timeout time.Duration
|
|
||||||
ClearCache bool
|
|
||||||
|
|
||||||
SkipDirs []string
|
|
||||||
SkipFiles []string
|
|
||||||
OfflineScan bool
|
|
||||||
|
|
||||||
// this field is populated in Init()
|
|
||||||
Target string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArtifactOption is the factory method to return artifact option
|
|
||||||
func NewArtifactOption(c *cli.Context) ArtifactOption {
|
|
||||||
return ArtifactOption{
|
|
||||||
Input: c.String("input"),
|
|
||||||
Timeout: c.Duration("timeout"),
|
|
||||||
ClearCache: c.Bool("clear-cache"),
|
|
||||||
SkipFiles: c.StringSlice("skip-files"),
|
|
||||||
SkipDirs: c.StringSlice("skip-dirs"),
|
|
||||||
OfflineScan: c.Bool("offline-scan"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize the CLI context for artifact scanning
|
|
||||||
func (c *ArtifactOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) (err error) {
|
|
||||||
if c.Input == "" && ctx.Args().Len() == 0 {
|
|
||||||
logger.Debug(`trivy requires at least 1 argument or --input option`)
|
|
||||||
_ = cli.ShowSubcommandHelp(ctx) // nolint: errcheck
|
|
||||||
os.Exit(0)
|
|
||||||
} else if ctx.Args().Len() > 1 && ctx.Command.Name != "kubernetes" {
|
|
||||||
logger.Error(`multiple targets cannot be specified`)
|
|
||||||
return xerrors.New("arguments error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Input == "" {
|
|
||||||
c.Target = ctx.Args().First()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package option_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zaptest/observer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifactOption_Init(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
logs []string
|
|
||||||
want option.ArtifactOption
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"alpine:3.10"},
|
|
||||||
want: option.ArtifactOption{
|
|
||||||
Target: "alpine:3.10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sad: multiple image names",
|
|
||||||
args: []string{"centos:7", "alpine:3.10"},
|
|
||||||
logs: []string{
|
|
||||||
"multiple targets cannot be specified",
|
|
||||||
},
|
|
||||||
wantErr: "arguments error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
core, obs := observer.New(zap.DebugLevel)
|
|
||||||
logger := zap.New(core)
|
|
||||||
|
|
||||||
app := cli.NewApp()
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
ctx := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
c := option.NewArtifactOption(ctx)
|
|
||||||
|
|
||||||
err := c.Init(ctx, logger.Sugar())
|
|
||||||
|
|
||||||
// tests log messages
|
|
||||||
var gotMessages []string
|
|
||||||
for _, entry := range obs.AllUntimed() {
|
|
||||||
gotMessages = append(gotMessages, entry.Message)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tt.logs, gotMessages, tt.name)
|
|
||||||
|
|
||||||
// test the error
|
|
||||||
switch {
|
|
||||||
case tt.wantErr != "":
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
assert.NoError(t, err, tt.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, tt.want, c, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CacheOption holds the options for cache
|
|
||||||
type CacheOption struct {
|
|
||||||
CacheBackend string
|
|
||||||
CacheTTL time.Duration
|
|
||||||
RedisOption
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedisOption holds the options for redis cache
|
|
||||||
type RedisOption struct {
|
|
||||||
RedisCACert string
|
|
||||||
RedisCert string
|
|
||||||
RedisKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCacheOption returns an instance of CacheOption
|
|
||||||
func NewCacheOption(c *cli.Context) CacheOption {
|
|
||||||
return CacheOption{
|
|
||||||
CacheBackend: c.String("cache-backend"),
|
|
||||||
CacheTTL: c.Duration("cache-ttl"),
|
|
||||||
RedisOption: RedisOption{
|
|
||||||
RedisCACert: c.String("redis-ca"),
|
|
||||||
RedisCert: c.String("redis-cert"),
|
|
||||||
RedisKey: c.String("redis-key"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize the CacheOption
|
|
||||||
func (c *CacheOption) Init() error {
|
|
||||||
// "redis://" or "fs" are allowed for now
|
|
||||||
// An empty value is also allowed for testability
|
|
||||||
if !strings.HasPrefix(c.CacheBackend, "redis://") &&
|
|
||||||
c.CacheBackend != "fs" && c.CacheBackend != "" {
|
|
||||||
return xerrors.Errorf("unsupported cache backend: %s", c.CacheBackend)
|
|
||||||
}
|
|
||||||
// if one of redis option not nil, make sure CA, cert, and key provided
|
|
||||||
if (RedisOption{}) != c.RedisOption {
|
|
||||||
if c.RedisCACert == "" || c.RedisCert == "" || c.RedisKey == "" {
|
|
||||||
return xerrors.Errorf("you must provide CA, cert and key file path when using tls")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheBackendMasked returns the redis connection string masking credentials
|
|
||||||
func (c *CacheOption) CacheBackendMasked() string {
|
|
||||||
endIndex := strings.Index(c.CacheBackend, "@")
|
|
||||||
if endIndex == -1 {
|
|
||||||
return c.CacheBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
startIndex := strings.Index(c.CacheBackend, "//")
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s****%s", c.CacheBackend[:startIndex+2], c.CacheBackend[endIndex:])
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package option_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewCacheOption(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want option.CacheOption
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"--cache-backend", "redis://localhost:6379"},
|
|
||||||
want: option.CacheOption{
|
|
||||||
CacheBackend: "redis://localhost:6379",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default",
|
|
||||||
args: []string{},
|
|
||||||
want: option.CacheOption{
|
|
||||||
CacheBackend: "fs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
app := &cli.App{}
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.String("cache-backend", "fs", "")
|
|
||||||
|
|
||||||
c := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
got := option.NewCacheOption(c)
|
|
||||||
assert.Equal(t, tt.want, got, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCacheOption_Init(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
backend string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "fs",
|
|
||||||
fields: fields{
|
|
||||||
backend: "fs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "redis",
|
|
||||||
fields: fields{
|
|
||||||
backend: "redis://localhost:6379",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sad path",
|
|
||||||
fields: fields{
|
|
||||||
backend: "unknown://",
|
|
||||||
},
|
|
||||||
wantErr: "unsupported cache backend: unknown://",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := &option.CacheOption{
|
|
||||||
CacheBackend: tt.fields.backend,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.Init()
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
assert.EqualError(t, err, tt.wantErr, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCacheOption_CacheBackendMasked(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
backend string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "redis cache backend masked",
|
|
||||||
fields: fields{
|
|
||||||
backend: "redis://root:password@localhost:6379",
|
|
||||||
},
|
|
||||||
want: "redis://****@localhost:6379",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "redis cache backend masked does nothing",
|
|
||||||
fields: fields{
|
|
||||||
backend: "redis://localhost:6379",
|
|
||||||
},
|
|
||||||
want: "redis://localhost:6379",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := &option.CacheOption{
|
|
||||||
CacheBackend: tt.fields.backend,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, tt.want, c.CacheBackendMasked())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigOption holds the options for config scanning
|
|
||||||
type ConfigOption struct {
|
|
||||||
FilePatterns []string
|
|
||||||
IncludeNonFailures bool
|
|
||||||
SkipPolicyUpdate bool
|
|
||||||
Trace bool
|
|
||||||
|
|
||||||
// Rego
|
|
||||||
PolicyPaths []string
|
|
||||||
DataPaths []string
|
|
||||||
PolicyNamespaces []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfigOption is the factory method to return config scanning options
|
|
||||||
func NewConfigOption(c *cli.Context) ConfigOption {
|
|
||||||
return ConfigOption{
|
|
||||||
IncludeNonFailures: c.Bool("include-non-failures"),
|
|
||||||
SkipPolicyUpdate: c.Bool("skip-policy-update"),
|
|
||||||
Trace: c.Bool("trace"),
|
|
||||||
FilePatterns: c.StringSlice("file-patterns"),
|
|
||||||
PolicyPaths: c.StringSlice("config-policy"),
|
|
||||||
DataPaths: c.StringSlice("config-data"),
|
|
||||||
PolicyNamespaces: c.StringSlice("policy-namespaces"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DBOption holds the options for trivy DB
|
|
||||||
type DBOption struct {
|
|
||||||
Reset bool
|
|
||||||
DownloadDBOnly bool
|
|
||||||
SkipDBUpdate bool
|
|
||||||
Light bool
|
|
||||||
NoProgress bool
|
|
||||||
DBRepository string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDBOption is the factory method to return the DBOption
|
|
||||||
func NewDBOption(c *cli.Context) DBOption {
|
|
||||||
return DBOption{
|
|
||||||
Reset: c.Bool("reset"),
|
|
||||||
DownloadDBOnly: c.Bool("download-db-only"),
|
|
||||||
SkipDBUpdate: c.Bool("skip-db-update"),
|
|
||||||
Light: c.Bool("light"),
|
|
||||||
NoProgress: c.Bool("no-progress"),
|
|
||||||
DBRepository: c.String("db-repository"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize the DBOption
|
|
||||||
func (c *DBOption) Init() (err error) {
|
|
||||||
if c.SkipDBUpdate && c.DownloadDBOnly {
|
|
||||||
return xerrors.New("--skip-db-update and --download-db-only options can not be specified both")
|
|
||||||
}
|
|
||||||
if c.Light {
|
|
||||||
log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package option_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewDBOption(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want option.DBOption
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"--reset", "--skip-db-update"},
|
|
||||||
want: option.DBOption{
|
|
||||||
Reset: true,
|
|
||||||
SkipDBUpdate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
app := &cli.App{}
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("reset", false, "")
|
|
||||||
set.Bool("skip-db-update", false, "")
|
|
||||||
|
|
||||||
c := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
got := option.NewDBOption(c)
|
|
||||||
assert.Equal(t, tt.want, got, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDBOption_Init(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Reset bool
|
|
||||||
DownloadDBOnly bool
|
|
||||||
SkipUpdate bool
|
|
||||||
Light bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
fields: fields{
|
|
||||||
Light: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sad path",
|
|
||||||
fields: fields{
|
|
||||||
DownloadDBOnly: true,
|
|
||||||
SkipUpdate: true,
|
|
||||||
},
|
|
||||||
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := &option.DBOption{
|
|
||||||
Reset: tt.fields.Reset,
|
|
||||||
DownloadDBOnly: tt.fields.DownloadDBOnly,
|
|
||||||
SkipDBUpdate: tt.fields.SkipUpdate,
|
|
||||||
Light: tt.fields.Light,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.Init()
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
assert.EqualError(t, err, tt.wantErr, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GlobalOption holds the global options for trivy
|
|
||||||
type GlobalOption struct {
|
|
||||||
Context *cli.Context
|
|
||||||
Logger *zap.SugaredLogger
|
|
||||||
|
|
||||||
AppVersion string
|
|
||||||
Quiet bool
|
|
||||||
Debug bool
|
|
||||||
CacheDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGlobalOption is the factory method to return GlobalOption
|
|
||||||
func NewGlobalOption(c *cli.Context) (GlobalOption, error) {
|
|
||||||
quiet := c.Bool("quiet")
|
|
||||||
debug := c.Bool("debug")
|
|
||||||
logger, err := log.NewLogger(debug, quiet)
|
|
||||||
if err != nil {
|
|
||||||
return GlobalOption{}, xerrors.New("failed to create a logger")
|
|
||||||
}
|
|
||||||
|
|
||||||
return GlobalOption{
|
|
||||||
Context: c,
|
|
||||||
Logger: logger,
|
|
||||||
|
|
||||||
AppVersion: c.App.Version,
|
|
||||||
Quiet: quiet,
|
|
||||||
Debug: debug,
|
|
||||||
CacheDir: c.String("cache-dir"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package option_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewGlobalConfig(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want option.GlobalOption
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"--quiet", "--debug"},
|
|
||||||
want: option.GlobalOption{
|
|
||||||
Quiet: true,
|
|
||||||
Debug: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
app := &cli.App{}
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("debug", false, "")
|
|
||||||
set.Bool("quiet", false, "")
|
|
||||||
|
|
||||||
c := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
got, err := option.NewGlobalOption(c)
|
|
||||||
require.NoError(t, err, err)
|
|
||||||
assert.Equal(t, tt.want.Quiet, got.Quiet, tt.name)
|
|
||||||
assert.Equal(t, tt.want.Debug, got.Debug, tt.name)
|
|
||||||
assert.Equal(t, tt.want.CacheDir, got.CacheDir, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImageOption holds the options for scanning images
|
|
||||||
type ImageOption struct {
|
|
||||||
ScanRemovedPkgs bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewImageOption is the factory method to return ImageOption
|
|
||||||
func NewImageOption(c *cli.Context) ImageOption {
|
|
||||||
return ImageOption{
|
|
||||||
ScanRemovedPkgs: c.Bool("removed-pkgs"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KubernetesOption holds the options for Kubernetes scanning
|
|
||||||
type KubernetesOption struct {
|
|
||||||
ClusterContext string
|
|
||||||
Namespace string
|
|
||||||
ReportFormat string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKubernetesOption is the factory method to return Kubernetes options
|
|
||||||
func NewKubernetesOption(c *cli.Context) KubernetesOption {
|
|
||||||
return KubernetesOption{
|
|
||||||
ClusterContext: c.String("context"),
|
|
||||||
Namespace: c.String("namespace"),
|
|
||||||
ReportFormat: c.String("report"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
type OtherOption struct {
|
|
||||||
Insecure bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOtherOption is the factory method to return other option
|
|
||||||
func NewOtherOption(c *cli.Context) OtherOption {
|
|
||||||
return OtherOption{
|
|
||||||
Insecure: c.Bool("insecure"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultTokenHeader = "Trivy-Token"
|
|
||||||
|
|
||||||
// RemoteOption holds options for client/server
|
|
||||||
type RemoteOption struct {
|
|
||||||
RemoteAddr string
|
|
||||||
customHeaders []string
|
|
||||||
token string
|
|
||||||
tokenHeader string
|
|
||||||
remote string // deprecated
|
|
||||||
|
|
||||||
// this field is populated in Init()
|
|
||||||
CustomHeaders http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteOption(c *cli.Context) RemoteOption {
|
|
||||||
r := RemoteOption{
|
|
||||||
RemoteAddr: c.String("server"),
|
|
||||||
customHeaders: c.StringSlice("custom-headers"),
|
|
||||||
token: c.String("token"),
|
|
||||||
tokenHeader: c.String("token-header"),
|
|
||||||
remote: c.String("remote"), // deprecated
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize the options for client/server mode
|
|
||||||
func (c *RemoteOption) Init(logger *zap.SugaredLogger) {
|
|
||||||
// for testability
|
|
||||||
defer func() {
|
|
||||||
c.token = ""
|
|
||||||
c.tokenHeader = ""
|
|
||||||
c.remote = ""
|
|
||||||
c.customHeaders = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
// for backward compatibility, should be removed in the future
|
|
||||||
if c.remote != "" {
|
|
||||||
c.RemoteAddr = c.remote
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.RemoteAddr == "" {
|
|
||||||
switch {
|
|
||||||
case len(c.customHeaders) > 0:
|
|
||||||
logger.Warn(`"--custom-header"" can be used only with "--server"`)
|
|
||||||
case c.token != "":
|
|
||||||
logger.Warn(`"--token" can be used only with "--server"`)
|
|
||||||
case c.tokenHeader != "" && c.tokenHeader != DefaultTokenHeader:
|
|
||||||
logger.Warn(`'--token-header' can be used only with "--server"`)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.CustomHeaders = splitCustomHeaders(c.customHeaders)
|
|
||||||
if c.token != "" {
|
|
||||||
c.CustomHeaders.Set(c.tokenHeader, c.token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitCustomHeaders(headers []string) http.Header {
|
|
||||||
result := make(http.Header)
|
|
||||||
for _, header := range headers {
|
|
||||||
// e.g. x-api-token:XXX
|
|
||||||
s := strings.SplitN(header, ":", 2)
|
|
||||||
if len(s) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result.Set(s[0], s[1])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_splitCustomHeaders(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
headers []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want http.Header
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: args{
|
|
||||||
headers: []string{"x-api-token:foo bar", "Authorization:user:password"},
|
|
||||||
},
|
|
||||||
want: http.Header{
|
|
||||||
"X-Api-Token": []string{"foo bar"},
|
|
||||||
"Authorization": []string{"user:password"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got := splitCustomHeaders(tt.args.headers)
|
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReportOption holds the options for reporting scan results
|
|
||||||
type ReportOption struct {
|
|
||||||
Format string
|
|
||||||
Template string
|
|
||||||
DependencyTree bool
|
|
||||||
|
|
||||||
IgnoreFile string
|
|
||||||
IgnoreUnfixed bool
|
|
||||||
ExitCode int
|
|
||||||
IgnorePolicy string
|
|
||||||
|
|
||||||
// these variables are not exported
|
|
||||||
vulnType string
|
|
||||||
securityChecks string
|
|
||||||
output string
|
|
||||||
severities string
|
|
||||||
|
|
||||||
// these variables are populated by Init()
|
|
||||||
VulnType []string
|
|
||||||
SecurityChecks []string
|
|
||||||
Output io.Writer
|
|
||||||
Severities []dbTypes.Severity
|
|
||||||
ListAllPkgs bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReportOption is the factory method to return ReportOption
|
|
||||||
func NewReportOption(c *cli.Context) ReportOption {
|
|
||||||
return ReportOption{
|
|
||||||
output: c.String("output"),
|
|
||||||
Format: c.String("format"),
|
|
||||||
DependencyTree: c.Bool("dependency-tree"),
|
|
||||||
Template: c.String("template"),
|
|
||||||
IgnorePolicy: c.String("ignore-policy"),
|
|
||||||
|
|
||||||
vulnType: c.String("vuln-type"),
|
|
||||||
securityChecks: c.String("security-checks"),
|
|
||||||
severities: c.String("severity"),
|
|
||||||
IgnoreFile: c.String("ignorefile"),
|
|
||||||
IgnoreUnfixed: c.Bool("ignore-unfixed"),
|
|
||||||
ExitCode: c.Int("exit-code"),
|
|
||||||
ListAllPkgs: c.Bool("list-all-pkgs"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the ReportOption
|
|
||||||
func (c *ReportOption) Init(output io.Writer, logger *zap.SugaredLogger) error {
|
|
||||||
if c.Template != "" {
|
|
||||||
if c.Format == "" {
|
|
||||||
logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
|
|
||||||
} else if c.Format != "template" {
|
|
||||||
logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", c.Format)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if c.Format == "template" {
|
|
||||||
logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "--list-all-pkgs" option is unavailable with "--format table".
|
|
||||||
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
|
|
||||||
if c.ListAllPkgs && c.Format == "table" {
|
|
||||||
logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "--dependency-tree" option is available only with "--format table".
|
|
||||||
if c.DependencyTree && c.Format != "table" {
|
|
||||||
logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.forceListAllPkgs(logger) {
|
|
||||||
c.ListAllPkgs = true
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Severities = splitSeverity(logger, c.severities)
|
|
||||||
|
|
||||||
if err := c.populateVulnTypes(); err != nil {
|
|
||||||
return xerrors.Errorf("vuln type: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.populateSecurityChecks(); err != nil {
|
|
||||||
return xerrors.Errorf("security checks: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for testability
|
|
||||||
c.severities = ""
|
|
||||||
c.vulnType = ""
|
|
||||||
c.securityChecks = ""
|
|
||||||
|
|
||||||
// The output is os.Stdout by default
|
|
||||||
if c.output != "" {
|
|
||||||
var err error
|
|
||||||
if output, err = os.Create(c.output); err != nil {
|
|
||||||
return xerrors.Errorf("failed to create an output file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Output = output
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ReportOption) populateVulnTypes() error {
|
|
||||||
if c.vulnType == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range strings.Split(c.vulnType, ",") {
|
|
||||||
if !slices.Contains(types.VulnTypes, v) {
|
|
||||||
return xerrors.Errorf("unknown vulnerability type (%s)", v)
|
|
||||||
}
|
|
||||||
c.VulnType = append(c.VulnType, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ReportOption) populateSecurityChecks() error {
|
|
||||||
if c.securityChecks == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range strings.Split(c.securityChecks, ",") {
|
|
||||||
if !slices.Contains(types.SecurityChecks, v) {
|
|
||||||
return xerrors.Errorf("unknown security check (%s)", v)
|
|
||||||
}
|
|
||||||
c.SecurityChecks = append(c.SecurityChecks, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ReportOption) forceListAllPkgs(logger *zap.SugaredLogger) bool {
|
|
||||||
if slices.Contains(supportedSbomFormats, c.Format) && !c.ListAllPkgs {
|
|
||||||
logger.Debugf("'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.DependencyTree {
|
|
||||||
logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitSeverity(logger *zap.SugaredLogger, severity string) []dbTypes.Severity {
|
|
||||||
logger.Debugf("Severities: %s", severity)
|
|
||||||
var severities []dbTypes.Severity
|
|
||||||
for _, s := range strings.Split(severity, ",") {
|
|
||||||
severity, err := dbTypes.NewSeverity(s)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("unknown severity option: %s", err)
|
|
||||||
}
|
|
||||||
severities = append(severities, severity)
|
|
||||||
}
|
|
||||||
return severities
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zaptest/observer"
|
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReportReportConfig_Init(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
output string
|
|
||||||
Format string
|
|
||||||
Template string
|
|
||||||
vulnType string
|
|
||||||
securityChecks string
|
|
||||||
severities string
|
|
||||||
IgnoreFile string
|
|
||||||
IgnoreUnfixed bool
|
|
||||||
listAllPksgs bool
|
|
||||||
ExitCode int
|
|
||||||
VulnType []string
|
|
||||||
Output *os.File
|
|
||||||
Severities []dbTypes.Severity
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args []string
|
|
||||||
logs []string
|
|
||||||
want ReportOption
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
fields: fields{
|
|
||||||
severities: "CRITICAL",
|
|
||||||
vulnType: "os",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
},
|
|
||||||
args: []string{"alpine:3.10"},
|
|
||||||
want: ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Output: os.Stdout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with an unknown severity",
|
|
||||||
fields: fields{
|
|
||||||
severities: "CRITICAL,INVALID",
|
|
||||||
vulnType: "os,library",
|
|
||||||
securityChecks: "config",
|
|
||||||
},
|
|
||||||
args: []string{"centos:7"},
|
|
||||||
logs: []string{
|
|
||||||
"unknown severity option: unknown severity: INVALID",
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckConfig},
|
|
||||||
Output: os.Stdout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with an cyclonedx",
|
|
||||||
fields: fields{
|
|
||||||
severities: "CRITICAL",
|
|
||||||
vulnType: "os,library",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
Format: report.FormatCycloneDX,
|
|
||||||
listAllPksgs: true,
|
|
||||||
},
|
|
||||||
args: []string{"centos:7"},
|
|
||||||
want: ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Format: report.FormatCycloneDX,
|
|
||||||
Output: os.Stdout,
|
|
||||||
ListAllPkgs: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path with an cyclonedx option list-all-pkgs is false",
|
|
||||||
fields: fields{
|
|
||||||
severities: "CRITICAL",
|
|
||||||
vulnType: "os,library",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
Format: "cyclonedx",
|
|
||||||
listAllPksgs: false,
|
|
||||||
debug: true,
|
|
||||||
},
|
|
||||||
args: []string{"centos:7"},
|
|
||||||
logs: []string{
|
|
||||||
"'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
|
|
||||||
"Severities: CRITICAL",
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
Format: "cyclonedx",
|
|
||||||
Output: os.Stdout,
|
|
||||||
ListAllPkgs: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --template enabled without --format",
|
|
||||||
fields: fields{
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
severities: "LOW",
|
|
||||||
vulnType: "os",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
},
|
|
||||||
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Output: os.Stdout,
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --template and --format json",
|
|
||||||
fields: fields{
|
|
||||||
Format: "json",
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
severities: "LOW",
|
|
||||||
vulnType: "os",
|
|
||||||
securityChecks: "config",
|
|
||||||
},
|
|
||||||
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Format: "json",
|
|
||||||
Output: os.Stdout,
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
|
||||||
Template: "@contrib/gitlab.tpl",
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckConfig},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --format template without --template",
|
|
||||||
fields: fields{
|
|
||||||
Format: "template",
|
|
||||||
severities: "LOW",
|
|
||||||
vulnType: "os",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
},
|
|
||||||
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
|
|
||||||
logs: []string{
|
|
||||||
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Format: "template",
|
|
||||||
Output: os.Stdout,
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid option combination: --list-all-pkgs with --format table",
|
|
||||||
fields: fields{
|
|
||||||
Format: "table",
|
|
||||||
severities: "LOW",
|
|
||||||
vulnType: "os",
|
|
||||||
securityChecks: "vuln",
|
|
||||||
listAllPksgs: true,
|
|
||||||
},
|
|
||||||
args: []string{"centos:7"},
|
|
||||||
logs: []string{
|
|
||||||
`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`,
|
|
||||||
},
|
|
||||||
want: ReportOption{
|
|
||||||
Format: "table",
|
|
||||||
Output: os.Stdout,
|
|
||||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
|
||||||
VulnType: []string{types.VulnTypeOS},
|
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
|
||||||
ListAllPkgs: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
level := zap.InfoLevel
|
|
||||||
if tt.fields.debug {
|
|
||||||
level = zap.DebugLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
core, obs := observer.New(level)
|
|
||||||
logger := zap.New(core)
|
|
||||||
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
c := &ReportOption{
|
|
||||||
output: tt.fields.output,
|
|
||||||
Format: tt.fields.Format,
|
|
||||||
Template: tt.fields.Template,
|
|
||||||
vulnType: tt.fields.vulnType,
|
|
||||||
securityChecks: tt.fields.securityChecks,
|
|
||||||
severities: tt.fields.severities,
|
|
||||||
IgnoreFile: tt.fields.IgnoreFile,
|
|
||||||
IgnoreUnfixed: tt.fields.IgnoreUnfixed,
|
|
||||||
ExitCode: tt.fields.ExitCode,
|
|
||||||
ListAllPkgs: tt.fields.listAllPksgs,
|
|
||||||
Output: tt.fields.Output,
|
|
||||||
}
|
|
||||||
err := c.Init(os.Stdout, logger.Sugar())
|
|
||||||
|
|
||||||
// tests log messages
|
|
||||||
var gotMessages []string
|
|
||||||
for _, entry := range obs.AllUntimed() {
|
|
||||||
gotMessages = append(gotMessages, entry.Message)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tt.logs, gotMessages, tt.name)
|
|
||||||
|
|
||||||
// test the error
|
|
||||||
switch {
|
|
||||||
case tt.wantErr != "":
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, err, tt.name)
|
|
||||||
assert.Equal(t, &tt.want, c, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
|
||||||
)
|
|
||||||
|
|
||||||
var supportedSbomFormats = []string{report.FormatCycloneDX, report.FormatSPDX, report.FormatSPDXJSON,
|
|
||||||
report.FormatGitHub}
|
|
||||||
|
|
||||||
// SbomOption holds the options for SBOM generation
|
|
||||||
type SbomOption struct {
|
|
||||||
ArtifactType string // deprecated
|
|
||||||
SbomFormat string // deprecated
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSbomOption is the factory method to return SBOM options
|
|
||||||
func NewSbomOption(c *cli.Context) SbomOption {
|
|
||||||
return SbomOption{
|
|
||||||
ArtifactType: c.String("artifact-type"),
|
|
||||||
SbomFormat: c.String("sbom-format"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize the CLI context for SBOM generation
|
|
||||||
func (c *SbomOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) error {
|
|
||||||
if ctx.Command.Name != "sbom" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ArtifactType != "" || c.SbomFormat != "" {
|
|
||||||
logger.Error("'trivy sbom' is now for scanning SBOM. " +
|
|
||||||
"See https://github.com/aquasecurity/trivy/discussions/2407 for the detail")
|
|
||||||
return xerrors.New("'--artifact-type' and '--sbom-format' are no longer available")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecretOption holds the options for secret scanning
|
|
||||||
type SecretOption struct {
|
|
||||||
SecretConfigPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSecretOption is the factory method to return secret options
|
|
||||||
func NewSecretOption(c *cli.Context) SecretOption {
|
|
||||||
return SecretOption{
|
|
||||||
SecretConfigPath: c.String("secret-config"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install installs a plugin
|
|
||||||
func Install(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := c.Args().First()
|
|
||||||
if _, err := plugin.Install(c.Context, url, true); err != nil {
|
|
||||||
return xerrors.Errorf("plugin install error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uninstall uninstalls the plugin
|
|
||||||
func Uninstall(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginName := c.Args().First()
|
|
||||||
if err := plugin.Uninstall(pluginName); err != nil {
|
|
||||||
return xerrors.Errorf("plugin uninstall error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Information displays information about the plugin
|
|
||||||
func Information(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize logger error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginName := c.Args().First()
|
|
||||||
info, err := plugin.Information(pluginName)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("plugin information display error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
|
|
||||||
return xerrors.Errorf("print error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List displays a list of all of installed plugins
|
|
||||||
func List(c *cli.Context) error {
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := plugin.List()
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("plugin list display error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
|
|
||||||
return xerrors.Errorf("print error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates an existing plugin
|
|
||||||
func Update(c *cli.Context) error {
|
|
||||||
if c.NArg() != 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginName := c.Args().First()
|
|
||||||
if err := plugin.Update(pluginName); err != nil {
|
|
||||||
return xerrors.Errorf("plugin update error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the plugin
|
|
||||||
func Run(c *cli.Context) error {
|
|
||||||
if c.NArg() < 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := c.Args().First()
|
|
||||||
args := c.Args().Tail()
|
|
||||||
return RunWithArgs(c.Context, url, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithArgs runs the plugin with arguments
|
|
||||||
func RunWithArgs(ctx context.Context, url string, args []string) error {
|
|
||||||
pl, err := plugin.Install(ctx, url, false)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("plugin install error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pl.Run(ctx, args); err != nil {
|
|
||||||
return xerrors.Errorf("unable to run %s plugin: %w", pl.Name, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCommands loads plugins as subcommands
|
|
||||||
func LoadCommands() cli.Commands {
|
|
||||||
var commands cli.Commands
|
|
||||||
plugins, err := plugin.LoadAll()
|
|
||||||
if err != nil {
|
|
||||||
log.Logger.Debugf("no plugins were loaded")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, p := range plugins {
|
|
||||||
p := p
|
|
||||||
cmd := &cli.Command{
|
|
||||||
Name: p.Name,
|
|
||||||
Usage: p.Usage,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
if err := initLogger(c); err != nil {
|
|
||||||
return xerrors.Errorf("initialize error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.Run(c.Context, c.Args().Slice()); err != nil {
|
|
||||||
return xerrors.Errorf("plugin error: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
SkipFlagParsing: true,
|
|
||||||
}
|
|
||||||
commands = append(commands, cmd)
|
|
||||||
}
|
|
||||||
return commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func initLogger(ctx *cli.Context) error {
|
|
||||||
conf, err := option.NewGlobalOption(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("config error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
|
|
||||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option holds the Trivy config
|
|
||||||
type Option struct {
|
|
||||||
option.GlobalOption
|
|
||||||
option.DBOption
|
|
||||||
option.CacheOption
|
|
||||||
option.OtherOption
|
|
||||||
|
|
||||||
Listen string
|
|
||||||
Token string
|
|
||||||
TokenHeader string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOption is the factory method to return config
|
|
||||||
func NewOption(c *cli.Context) Option {
|
|
||||||
// the error is ignored because logger is unnecessary
|
|
||||||
gc, _ := option.NewGlobalOption(c) // nolint: errcheck
|
|
||||||
return Option{
|
|
||||||
GlobalOption: gc,
|
|
||||||
DBOption: option.NewDBOption(c),
|
|
||||||
CacheOption: option.NewCacheOption(c),
|
|
||||||
OtherOption: option.NewOtherOption(c),
|
|
||||||
|
|
||||||
Listen: c.String("listen"),
|
|
||||||
Token: c.String("token"),
|
|
||||||
TokenHeader: c.String("token-header"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the config
|
|
||||||
func (c *Option) Init() (err error) {
|
|
||||||
if err := c.DBOption.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.CacheOption.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package server_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want server.Option
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"-quiet", "--no-progress", "--reset", "--skip-db-update", "--listen", "localhost:8080"},
|
|
||||||
want: server.Option{
|
|
||||||
GlobalOption: option.GlobalOption{
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
DBOption: option.DBOption{
|
|
||||||
Reset: true,
|
|
||||||
SkipDBUpdate: true,
|
|
||||||
NoProgress: true,
|
|
||||||
},
|
|
||||||
Listen: "localhost:8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
app := &cli.App{}
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("quiet", false, "")
|
|
||||||
set.Bool("no-progress", false, "")
|
|
||||||
set.Bool("reset", false, "")
|
|
||||||
set.Bool("skip-db-update", false, "")
|
|
||||||
set.String("listen", "", "")
|
|
||||||
|
|
||||||
ctx := cli.NewContext(app, set, nil)
|
|
||||||
_ = set.Parse(tt.args)
|
|
||||||
|
|
||||||
tt.want.GlobalOption.Context = ctx
|
|
||||||
|
|
||||||
got := server.NewOption(ctx)
|
|
||||||
assert.Equal(t, tt.want.GlobalOption.Quiet, got.Quiet, tt.name)
|
|
||||||
assert.Equal(t, tt.want.DBOption, got.DBOption, tt.name)
|
|
||||||
assert.Equal(t, tt.want.Listen, got.Listen, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Init(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
globalConfig option.GlobalOption
|
|
||||||
dbConfig option.DBOption
|
|
||||||
args []string
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
args: []string{"alpine:3.10"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy path: reset",
|
|
||||||
dbConfig: option.DBOption{
|
|
||||||
Reset: true,
|
|
||||||
},
|
|
||||||
args: []string{"alpine:3.10"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sad: skip and download db",
|
|
||||||
dbConfig: option.DBOption{
|
|
||||||
SkipDBUpdate: true,
|
|
||||||
DownloadDBOnly: true,
|
|
||||||
},
|
|
||||||
args: []string{"alpine:3.10"},
|
|
||||||
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := &server.Option{
|
|
||||||
DBOption: tt.dbConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.Init()
|
|
||||||
|
|
||||||
// test the error
|
|
||||||
switch {
|
|
||||||
case tt.wantErr != "":
|
|
||||||
require.NotNil(t, err, tt.name)
|
|
||||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
assert.NoError(t, err, tt.name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"context"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/module"
|
"github.com/aquasecurity/trivy/pkg/module"
|
||||||
rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server"
|
rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server"
|
||||||
@@ -13,53 +15,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Run runs the scan
|
// Run runs the scan
|
||||||
func Run(ctx *cli.Context) error {
|
func Run(ctx context.Context, opts flag.Options) (err error) {
|
||||||
return run(NewOption(ctx))
|
if err = log.InitLogger(opts.Debug, opts.Quiet); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func run(c Option) (err error) {
|
|
||||||
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
|
|
||||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize config
|
|
||||||
if err = c.Init(); err != nil {
|
|
||||||
return xerrors.Errorf("failed to initialize options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure cache dir
|
// configure cache dir
|
||||||
utils.SetCacheDir(c.CacheDir)
|
utils.SetCacheDir(opts.CacheDir)
|
||||||
cache, err := operation.NewCache(c.CacheOption)
|
cache, err := operation.NewCache(opts.CacheOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("server cache error: %w", err)
|
return xerrors.Errorf("server cache error: %w", err)
|
||||||
}
|
}
|
||||||
defer cache.Close()
|
defer cache.Close()
|
||||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||||
|
|
||||||
if c.Reset {
|
if opts.Reset {
|
||||||
return cache.ClearDB()
|
return cache.ClearDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
// download the database file
|
// download the database file
|
||||||
if err = operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, true, c.Insecure, c.SkipDBUpdate); err != nil {
|
if err = operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository,
|
||||||
|
true, opts.Insecure, opts.SkipDBUpdate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.DownloadDBOnly {
|
if opts.DownloadDBOnly {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = db.Init(c.CacheDir); err != nil {
|
if err = db.Init(opts.CacheDir); err != nil {
|
||||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize WASM modules
|
// Initialize WASM modules
|
||||||
m, err := module.NewManager(c.Context.Context)
|
m, err := module.NewManager(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("WASM module error: %w", err)
|
return xerrors.Errorf("WASM module error: %w", err)
|
||||||
}
|
}
|
||||||
m.Register()
|
m.Register()
|
||||||
|
|
||||||
server := rpcServer.NewServer(c.AppVersion, c.Listen, c.CacheDir, c.Token, c.TokenHeader, c.DBRepository)
|
server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, opts.DBRepository)
|
||||||
return server.ListenAndServe(cache, c.Insecure)
|
return server.ListenAndServe(cache, opts.Insecure)
|
||||||
}
|
}
|
||||||
|
|||||||
155
pkg/flag/cache_flags.go
Normal file
155
pkg/flag/cache_flags.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// e.g. config yaml
|
||||||
|
// cache:
|
||||||
|
// clear: true
|
||||||
|
// backend: "redis://localhost:6379"
|
||||||
|
// redis:
|
||||||
|
// ca: ca-cert.pem
|
||||||
|
// cert: cert.pem
|
||||||
|
// key: key.pem
|
||||||
|
var (
|
||||||
|
ClearCacheFlag = Flag{
|
||||||
|
Name: "clear-cache",
|
||||||
|
ConfigName: "cache.clear",
|
||||||
|
Value: false,
|
||||||
|
Usage: "clear image caches without scanning",
|
||||||
|
}
|
||||||
|
CacheBackendFlag = Flag{
|
||||||
|
Name: "cache-backend",
|
||||||
|
ConfigName: "cache.backend",
|
||||||
|
Value: "fs",
|
||||||
|
Usage: "cache backend (e.g. redis://localhost:6379)",
|
||||||
|
}
|
||||||
|
CacheTTLFlag = Flag{
|
||||||
|
Name: "cache-ttl",
|
||||||
|
ConfigName: "cache.ttl",
|
||||||
|
Value: time.Duration(0),
|
||||||
|
Usage: "cache TTL when using redis as cache backend",
|
||||||
|
}
|
||||||
|
RedisCACertFlag = Flag{
|
||||||
|
Name: "redis-ca",
|
||||||
|
ConfigName: "cache.redis.ca",
|
||||||
|
Value: "",
|
||||||
|
Usage: "redis ca file location, if using redis as cache backend",
|
||||||
|
}
|
||||||
|
RedisCertFlag = Flag{
|
||||||
|
Name: "redis-cert",
|
||||||
|
ConfigName: "cache.redis.cert",
|
||||||
|
Value: "",
|
||||||
|
Usage: "redis certificate file location, if using redis as cache backend",
|
||||||
|
}
|
||||||
|
RedisKeyFlag = Flag{
|
||||||
|
Name: "redis-key",
|
||||||
|
ConfigName: "cache.redis.key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "redis key file location, if using redis as cache backend",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// CacheFlagGroup composes common printer flag structs used for commands requiring cache logic.
|
||||||
|
type CacheFlagGroup struct {
|
||||||
|
ClearCache *Flag
|
||||||
|
CacheBackend *Flag
|
||||||
|
CacheTTL *Flag
|
||||||
|
|
||||||
|
RedisCACert *Flag
|
||||||
|
RedisCert *Flag
|
||||||
|
RedisKey *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type CacheOptions struct {
|
||||||
|
ClearCache bool
|
||||||
|
CacheBackend string
|
||||||
|
CacheTTL time.Duration
|
||||||
|
RedisOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisOptions holds the options for redis cache
|
||||||
|
type RedisOptions struct {
|
||||||
|
RedisCACert string
|
||||||
|
RedisCert string
|
||||||
|
RedisKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCacheFlagGroup returns a default CacheFlagGroup
|
||||||
|
func NewCacheFlagGroup() *CacheFlagGroup {
|
||||||
|
return &CacheFlagGroup{
|
||||||
|
ClearCache: lo.ToPtr(ClearCacheFlag),
|
||||||
|
CacheBackend: lo.ToPtr(CacheBackendFlag),
|
||||||
|
CacheTTL: lo.ToPtr(CacheTTLFlag),
|
||||||
|
RedisCACert: lo.ToPtr(RedisCACertFlag),
|
||||||
|
RedisCert: lo.ToPtr(RedisCertFlag),
|
||||||
|
RedisKey: lo.ToPtr(RedisKeyFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CacheFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.ClearCache, f.CacheBackend, f.CacheTTL, f.RedisCACert, f.RedisCert, f.RedisKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CacheFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CacheFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CacheFlagGroup) ToOptions() (CacheOptions, error) {
|
||||||
|
cacheBackend := getString(f.CacheBackend)
|
||||||
|
redisOptions := RedisOptions{
|
||||||
|
RedisCACert: getString(f.RedisCACert),
|
||||||
|
RedisCert: getString(f.RedisCert),
|
||||||
|
RedisKey: getString(f.RedisKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
// "redis://" or "fs" are allowed for now
|
||||||
|
// An empty value is also allowed for testability
|
||||||
|
if !strings.HasPrefix(cacheBackend, "redis://") &&
|
||||||
|
cacheBackend != "fs" && cacheBackend != "" {
|
||||||
|
return CacheOptions{}, xerrors.Errorf("unsupported cache backend: %s", cacheBackend)
|
||||||
|
}
|
||||||
|
// if one of redis option not nil, make sure CA, cert, and key provided
|
||||||
|
if !lo.IsEmpty(redisOptions) {
|
||||||
|
if redisOptions.RedisCACert == "" || redisOptions.RedisCert == "" || redisOptions.RedisKey == "" {
|
||||||
|
return CacheOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CacheOptions{
|
||||||
|
ClearCache: getBool(f.ClearCache),
|
||||||
|
CacheBackend: cacheBackend,
|
||||||
|
CacheTTL: getDuration(f.CacheTTL),
|
||||||
|
RedisOptions: redisOptions,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheBackendMasked returns the redis connection string masking credentials
|
||||||
|
func (o *CacheOptions) CacheBackendMasked() string {
|
||||||
|
endIndex := strings.Index(o.CacheBackend, "@")
|
||||||
|
if endIndex == -1 {
|
||||||
|
return o.CacheBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex := strings.Index(o.CacheBackend, "//")
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s****%s", o.CacheBackend[:startIndex+2], o.CacheBackend[endIndex:])
|
||||||
|
}
|
||||||
145
pkg/flag/cache_flags_test.go
Normal file
145
pkg/flag/cache_flags_test.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheFlagGroup_ToOptions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
ClearCache bool
|
||||||
|
CacheBackend string
|
||||||
|
CacheTTL time.Duration
|
||||||
|
RedisCACert string
|
||||||
|
RedisCert string
|
||||||
|
RedisKey string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want flag.CacheOptions
|
||||||
|
assertion require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "fs",
|
||||||
|
fields: fields{
|
||||||
|
CacheBackend: "fs",
|
||||||
|
},
|
||||||
|
want: flag.CacheOptions{
|
||||||
|
CacheBackend: "fs",
|
||||||
|
},
|
||||||
|
assertion: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis",
|
||||||
|
fields: fields{
|
||||||
|
CacheBackend: "redis://localhost:6379",
|
||||||
|
},
|
||||||
|
want: flag.CacheOptions{
|
||||||
|
CacheBackend: "redis://localhost:6379",
|
||||||
|
},
|
||||||
|
assertion: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis tls",
|
||||||
|
fields: fields{
|
||||||
|
CacheBackend: "redis://localhost:6379",
|
||||||
|
RedisCACert: "ca-cert.pem",
|
||||||
|
RedisCert: "cert.pem",
|
||||||
|
RedisKey: "key.pem",
|
||||||
|
},
|
||||||
|
want: flag.CacheOptions{
|
||||||
|
CacheBackend: "redis://localhost:6379",
|
||||||
|
RedisOptions: flag.RedisOptions{
|
||||||
|
RedisCACert: "ca-cert.pem",
|
||||||
|
RedisCert: "cert.pem",
|
||||||
|
RedisKey: "key.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
assertion: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown backend",
|
||||||
|
fields: fields{
|
||||||
|
CacheBackend: "unknown",
|
||||||
|
},
|
||||||
|
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
|
||||||
|
require.ErrorContains(t, err, "unsupported cache backend")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sad redis tls",
|
||||||
|
fields: fields{
|
||||||
|
CacheBackend: "redis://localhost:6379",
|
||||||
|
RedisCACert: "ca-cert.pem",
|
||||||
|
},
|
||||||
|
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
|
||||||
|
require.ErrorContains(t, err, "you must provide Redis CA")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
viper.Set(flag.ClearCacheFlag.ConfigName, tt.fields.ClearCache)
|
||||||
|
viper.Set(flag.CacheBackendFlag.ConfigName, tt.fields.CacheBackend)
|
||||||
|
viper.Set(flag.CacheTTLFlag.ConfigName, tt.fields.CacheTTL)
|
||||||
|
viper.Set(flag.RedisCACertFlag.ConfigName, tt.fields.RedisCACert)
|
||||||
|
viper.Set(flag.RedisCertFlag.ConfigName, tt.fields.RedisCert)
|
||||||
|
viper.Set(flag.RedisKeyFlag.ConfigName, tt.fields.RedisKey)
|
||||||
|
|
||||||
|
f := &flag.CacheFlagGroup{
|
||||||
|
ClearCache: &flag.ClearCacheFlag,
|
||||||
|
CacheBackend: &flag.CacheBackendFlag,
|
||||||
|
CacheTTL: &flag.CacheTTLFlag,
|
||||||
|
RedisCACert: &flag.RedisCACertFlag,
|
||||||
|
RedisCert: &flag.RedisCertFlag,
|
||||||
|
RedisKey: &flag.RedisKeyFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := f.ToOptions()
|
||||||
|
tt.assertion(t, err)
|
||||||
|
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheOptions_CacheBackendMasked(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
backend string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "redis cache backend masked",
|
||||||
|
fields: fields{
|
||||||
|
backend: "redis://root:password@localhost:6379",
|
||||||
|
},
|
||||||
|
want: "redis://****@localhost:6379",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis cache backend masked does nothing",
|
||||||
|
fields: fields{
|
||||||
|
backend: "redis://localhost:6379",
|
||||||
|
},
|
||||||
|
want: "redis://localhost:6379",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &flag.CacheOptions{
|
||||||
|
CacheBackend: tt.fields.backend,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, c.CacheBackendMasked())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
122
pkg/flag/db_flags.go
Normal file
122
pkg/flag/db_flags.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultDBRepository = "ghcr.io/aquasecurity/trivy-db"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ResetFlag = Flag{
|
||||||
|
Name: "reset",
|
||||||
|
ConfigName: "reset",
|
||||||
|
Value: false,
|
||||||
|
Usage: "remove all caches and database",
|
||||||
|
}
|
||||||
|
DownloadDBOnlyFlag = Flag{
|
||||||
|
Name: "download-db-only",
|
||||||
|
ConfigName: "db.download-only",
|
||||||
|
Value: false,
|
||||||
|
Usage: "download/update vulnerability database but don't run a scan",
|
||||||
|
}
|
||||||
|
SkipDBUpdateFlag = Flag{
|
||||||
|
Name: "skip-db-update",
|
||||||
|
ConfigName: "db.skip-update",
|
||||||
|
Value: false,
|
||||||
|
Usage: "skip updating vulnerability database",
|
||||||
|
}
|
||||||
|
NoProgressFlag = Flag{
|
||||||
|
Name: "no-progress",
|
||||||
|
ConfigName: "db.no-progress",
|
||||||
|
Value: false,
|
||||||
|
Usage: "suppress progress bar",
|
||||||
|
}
|
||||||
|
DBRepositoryFlag = Flag{
|
||||||
|
Name: "db-repository",
|
||||||
|
ConfigName: "db.repository",
|
||||||
|
Value: defaultDBRepository,
|
||||||
|
Usage: "OCI repository to retrieve trivy-db from\"",
|
||||||
|
}
|
||||||
|
LightFlag = Flag{
|
||||||
|
Name: "light",
|
||||||
|
ConfigName: "db.light",
|
||||||
|
Value: false,
|
||||||
|
Usage: "deprecated",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// DBFlagGroup composes common printer flag structs used for commands requiring DB logic.
|
||||||
|
type DBFlagGroup struct {
|
||||||
|
Reset *Flag
|
||||||
|
DownloadDBOnly *Flag
|
||||||
|
SkipDBUpdate *Flag
|
||||||
|
NoProgress *Flag
|
||||||
|
DBRepository *Flag
|
||||||
|
Light *Flag // deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBOptions struct {
|
||||||
|
Reset bool
|
||||||
|
DownloadDBOnly bool
|
||||||
|
SkipDBUpdate bool
|
||||||
|
NoProgress bool
|
||||||
|
DBRepository string
|
||||||
|
Light bool // deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDBFlagGroup returns a default DBFlagGroup
|
||||||
|
func NewDBFlagGroup() *DBFlagGroup {
|
||||||
|
return &DBFlagGroup{
|
||||||
|
Reset: lo.ToPtr(ResetFlag),
|
||||||
|
DownloadDBOnly: lo.ToPtr(DownloadDBOnlyFlag),
|
||||||
|
SkipDBUpdate: lo.ToPtr(SkipDBUpdateFlag),
|
||||||
|
Light: lo.ToPtr(LightFlag),
|
||||||
|
NoProgress: lo.ToPtr(NoProgressFlag),
|
||||||
|
DBRepository: lo.ToPtr(DBRepositoryFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DBFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.Reset, f.DownloadDBOnly, f.SkipDBUpdate, f.NoProgress, f.DBRepository, f.Light}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DBFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DBFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
|
||||||
|
skipDBUpdate := getBool(f.SkipDBUpdate)
|
||||||
|
downloadDBOnly := getBool(f.DownloadDBOnly)
|
||||||
|
light := getBool(f.Light)
|
||||||
|
|
||||||
|
if downloadDBOnly && skipDBUpdate {
|
||||||
|
return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both")
|
||||||
|
}
|
||||||
|
if light {
|
||||||
|
log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649")
|
||||||
|
}
|
||||||
|
|
||||||
|
return DBOptions{
|
||||||
|
Reset: getBool(f.Reset),
|
||||||
|
DownloadDBOnly: downloadDBOnly,
|
||||||
|
SkipDBUpdate: skipDBUpdate,
|
||||||
|
Light: light,
|
||||||
|
NoProgress: getBool(f.NoProgress),
|
||||||
|
DBRepository: getString(f.DBRepository),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
94
pkg/flag/db_flags_test.go
Normal file
94
pkg/flag/db_flags_test.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDBFlagGroup_ToOptions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
SkipDBUpdate bool
|
||||||
|
DownloadDBOnly bool
|
||||||
|
Light bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want flag.DBOptions
|
||||||
|
wantLogs []string
|
||||||
|
assertion require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy",
|
||||||
|
fields: fields{
|
||||||
|
SkipDBUpdate: true,
|
||||||
|
DownloadDBOnly: false,
|
||||||
|
},
|
||||||
|
want: flag.DBOptions{
|
||||||
|
SkipDBUpdate: true,
|
||||||
|
DownloadDBOnly: false,
|
||||||
|
},
|
||||||
|
assertion: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "light",
|
||||||
|
fields: fields{
|
||||||
|
Light: true,
|
||||||
|
},
|
||||||
|
want: flag.DBOptions{
|
||||||
|
Light: true,
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
"'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649",
|
||||||
|
},
|
||||||
|
assertion: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sad",
|
||||||
|
fields: fields{
|
||||||
|
SkipDBUpdate: true,
|
||||||
|
DownloadDBOnly: true,
|
||||||
|
},
|
||||||
|
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
|
||||||
|
require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
level := zap.WarnLevel
|
||||||
|
core, obs := observer.New(level)
|
||||||
|
log.Logger = zap.New(core).Sugar()
|
||||||
|
|
||||||
|
viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate)
|
||||||
|
viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly)
|
||||||
|
viper.Set(flag.LightFlag.ConfigName, tt.fields.Light)
|
||||||
|
|
||||||
|
// Assert options
|
||||||
|
f := &flag.DBFlagGroup{
|
||||||
|
DownloadDBOnly: &flag.DownloadDBOnlyFlag,
|
||||||
|
SkipDBUpdate: &flag.SkipDBUpdateFlag,
|
||||||
|
Light: &flag.LightFlag,
|
||||||
|
}
|
||||||
|
got, err := f.ToOptions()
|
||||||
|
tt.assertion(t, err)
|
||||||
|
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||||
|
|
||||||
|
// Assert log messages
|
||||||
|
var gotMessages []string
|
||||||
|
for _, entry := range obs.AllUntimed() {
|
||||||
|
gotMessages = append(gotMessages, entry.Message)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
131
pkg/flag/global_flags.go
Normal file
131
pkg/flag/global_flags.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConfigFileFlag = Flag{
|
||||||
|
Name: "config",
|
||||||
|
ConfigName: "config",
|
||||||
|
Shorthand: "c",
|
||||||
|
Value: "trivy.yaml",
|
||||||
|
Usage: "config path",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
ShowVersionFlag = Flag{
|
||||||
|
Name: "version",
|
||||||
|
ConfigName: "version",
|
||||||
|
Shorthand: "v",
|
||||||
|
Value: false,
|
||||||
|
Usage: "show version",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
QuietFlag = Flag{
|
||||||
|
Name: "quiet",
|
||||||
|
ConfigName: "quiet",
|
||||||
|
Shorthand: "q",
|
||||||
|
Value: false,
|
||||||
|
Usage: "suppress progress bar and log output",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
DebugFlag = Flag{
|
||||||
|
Name: "debug",
|
||||||
|
ConfigName: "debug",
|
||||||
|
Shorthand: "d",
|
||||||
|
Value: false,
|
||||||
|
Usage: "debug mode",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
InsecureFlag = Flag{
|
||||||
|
Name: "insecure",
|
||||||
|
ConfigName: "insecure",
|
||||||
|
Value: false,
|
||||||
|
Usage: "allow insecure server connections when using TLS",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
TimeoutFlag = Flag{
|
||||||
|
Name: "timeout",
|
||||||
|
ConfigName: "timeout",
|
||||||
|
Value: time.Second * 300, // 5 mins
|
||||||
|
Usage: "timeout",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
CacheDirFlag = Flag{
|
||||||
|
Name: "cache-dir",
|
||||||
|
ConfigName: "cache.dir",
|
||||||
|
Value: utils.DefaultCacheDir(),
|
||||||
|
Usage: "cache directory",
|
||||||
|
Persistent: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GlobalFlagGroup composes global flags
|
||||||
|
type GlobalFlagGroup struct {
|
||||||
|
ConfigFile *Flag
|
||||||
|
ShowVersion *Flag // spf13/cobra can't override the logic of version printing like VersionPrinter in urfave/cli. -v needs to be defined ourselves.
|
||||||
|
Quiet *Flag
|
||||||
|
Debug *Flag
|
||||||
|
Insecure *Flag
|
||||||
|
Timeout *Flag
|
||||||
|
CacheDir *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalOptions defines flags and other configuration parameters for all the subcommands
|
||||||
|
type GlobalOptions struct {
|
||||||
|
ConfigFile string
|
||||||
|
ShowVersion bool
|
||||||
|
Quiet bool
|
||||||
|
Debug bool
|
||||||
|
Insecure bool
|
||||||
|
Timeout time.Duration
|
||||||
|
CacheDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGlobalFlagGroup() *GlobalFlagGroup {
|
||||||
|
return &GlobalFlagGroup{
|
||||||
|
ConfigFile: &ConfigFileFlag,
|
||||||
|
ShowVersion: &ShowVersionFlag,
|
||||||
|
Quiet: &QuietFlag,
|
||||||
|
Debug: &DebugFlag,
|
||||||
|
Insecure: &InsecureFlag,
|
||||||
|
Timeout: &TimeoutFlag,
|
||||||
|
CacheDir: &CacheDirFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GlobalFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.ConfigFile, f.ShowVersion, f.Quiet, f.Debug, f.Insecure, f.Timeout, f.CacheDir}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GlobalFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GlobalFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GlobalFlagGroup) ToOptions() GlobalOptions {
|
||||||
|
return GlobalOptions{
|
||||||
|
ConfigFile: getString(f.ConfigFile),
|
||||||
|
ShowVersion: getBool(f.ShowVersion),
|
||||||
|
Quiet: getBool(f.Quiet),
|
||||||
|
Debug: getBool(f.Debug),
|
||||||
|
Insecure: getBool(f.Insecure),
|
||||||
|
Timeout: viper.GetDuration(f.Timeout.ConfigName),
|
||||||
|
CacheDir: getString(f.CacheDir),
|
||||||
|
}
|
||||||
|
}
|
||||||
68
pkg/flag/image_flags.go
Normal file
68
pkg/flag/image_flags.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// e.g. config yaml
|
||||||
|
// image:
|
||||||
|
// removed-pkgs: true
|
||||||
|
// input: "/path/to/alpine"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ScanRemovedPkgsFlag = Flag{
|
||||||
|
Name: "removed-pkgs",
|
||||||
|
ConfigName: "image.removed-pkgs",
|
||||||
|
Value: false,
|
||||||
|
Usage: "detect vulnerabilities of removed packages (only for Alpine)",
|
||||||
|
}
|
||||||
|
InputFlag = Flag{
|
||||||
|
Name: "input",
|
||||||
|
ConfigName: "image.input",
|
||||||
|
Value: "",
|
||||||
|
Usage: "input file path instead of image name",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageFlagGroup struct {
|
||||||
|
Input *Flag // local image archive
|
||||||
|
ScanRemovedPkgs *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageOptions struct {
|
||||||
|
Input string
|
||||||
|
ScanRemovedPkgs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageFlagGroup() *ImageFlagGroup {
|
||||||
|
return &ImageFlagGroup{
|
||||||
|
Input: &InputFlag,
|
||||||
|
ScanRemovedPkgs: &ScanRemovedPkgsFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ImageFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.Input, f.ScanRemovedPkgs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ImageFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ImageFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ImageFlagGroup) ToOptions() ImageOptions {
|
||||||
|
return ImageOptions{
|
||||||
|
Input: getString(f.Input),
|
||||||
|
ScanRemovedPkgs: getBool(f.ScanRemovedPkgs),
|
||||||
|
}
|
||||||
|
}
|
||||||
64
pkg/flag/kubernetes_flags.go
Normal file
64
pkg/flag/kubernetes_flags.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ClusterContextFlag = Flag{
|
||||||
|
Name: "context",
|
||||||
|
ConfigName: "kubernetes.context",
|
||||||
|
Value: "",
|
||||||
|
Usage: "specify a context to scan",
|
||||||
|
}
|
||||||
|
K8sNamespaceFlag = Flag{
|
||||||
|
Name: "namespace",
|
||||||
|
ConfigName: "kubernetes.namespace",
|
||||||
|
Value: "",
|
||||||
|
Usage: "specify a namespace to sca",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type K8sFlagGroup struct {
|
||||||
|
ClusterContext *Flag
|
||||||
|
Namespace *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type K8sOptions struct {
|
||||||
|
ClusterContext string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewK8sFlagGroup() *K8sFlagGroup {
|
||||||
|
return &K8sFlagGroup{
|
||||||
|
ClusterContext: lo.ToPtr(ClusterContextFlag),
|
||||||
|
Namespace: lo.ToPtr(K8sNamespaceFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *K8sFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.ClusterContext, f.Namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *K8sFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *K8sFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *K8sFlagGroup) ToOptions() K8sOptions {
|
||||||
|
return K8sOptions{
|
||||||
|
ClusterContext: getString(f.ClusterContext),
|
||||||
|
Namespace: getString(f.Namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
130
pkg/flag/misconf_flags.go
Normal file
130
pkg/flag/misconf_flags.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// e.g. config yaml
|
||||||
|
// misconfiguration:
|
||||||
|
// trace: true
|
||||||
|
// config-policy: "custom-policy/policy"
|
||||||
|
// policy-namespaces: "user"
|
||||||
|
var (
|
||||||
|
FilePatternsFlag = Flag{
|
||||||
|
Name: "file-patterns",
|
||||||
|
ConfigName: "misconfiguration.file-patterns",
|
||||||
|
Value: []string{},
|
||||||
|
Usage: "specify config file patterns, available with '--security-checks config'",
|
||||||
|
}
|
||||||
|
IncludeNonFailuresFlag = Flag{
|
||||||
|
Name: "include-non-failures",
|
||||||
|
ConfigName: "misconfiguration.include-non-failures",
|
||||||
|
Value: false,
|
||||||
|
Usage: "include successes and exceptions, available with '--security-checks config'",
|
||||||
|
}
|
||||||
|
SkipPolicyUpdateFlag = Flag{
|
||||||
|
Name: "skip-policy-update",
|
||||||
|
ConfigName: "misconfiguration.skip-policy-update",
|
||||||
|
Value: false,
|
||||||
|
Usage: "deprecated",
|
||||||
|
}
|
||||||
|
TraceFlag = Flag{
|
||||||
|
Name: "trace",
|
||||||
|
ConfigName: "misconfiguration.trace",
|
||||||
|
Value: false,
|
||||||
|
Usage: "enable more verbose trace output for custom queries",
|
||||||
|
}
|
||||||
|
ConfigPolicyFlag = Flag{
|
||||||
|
Name: "config-policy",
|
||||||
|
ConfigName: "misconfiguration.config-policy",
|
||||||
|
Value: []string{},
|
||||||
|
Usage: "specify paths to the Rego policy files directory, applying config files",
|
||||||
|
}
|
||||||
|
ConfigDataFlag = Flag{
|
||||||
|
Name: "config-data",
|
||||||
|
ConfigName: "misconfiguration.config-data",
|
||||||
|
Value: []string{},
|
||||||
|
Usage: "specify paths from which data for the Rego policies will be recursively loaded",
|
||||||
|
}
|
||||||
|
PolicyNamespaceFlag = Flag{
|
||||||
|
Name: "policy-namespaces",
|
||||||
|
ConfigName: "misconfiguration.policy-namespaces",
|
||||||
|
Value: []string{},
|
||||||
|
Usage: "Rego namespaces",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// MisconfFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning.
|
||||||
|
type MisconfFlagGroup struct {
|
||||||
|
FilePatterns *Flag
|
||||||
|
IncludeNonFailures *Flag
|
||||||
|
SkipPolicyUpdate *Flag // deprecated
|
||||||
|
Trace *Flag
|
||||||
|
|
||||||
|
// Rego
|
||||||
|
PolicyPaths *Flag
|
||||||
|
DataPaths *Flag
|
||||||
|
PolicyNamespaces *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type MisconfOptions struct {
|
||||||
|
FilePatterns []string
|
||||||
|
IncludeNonFailures bool
|
||||||
|
SkipPolicyUpdate bool // deprecated
|
||||||
|
Trace bool
|
||||||
|
|
||||||
|
// Rego
|
||||||
|
PolicyPaths []string
|
||||||
|
DataPaths []string
|
||||||
|
PolicyNamespaces []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMisconfFlagGroup() *MisconfFlagGroup {
|
||||||
|
return &MisconfFlagGroup{
|
||||||
|
FilePatterns: lo.ToPtr(FilePatternsFlag),
|
||||||
|
IncludeNonFailures: lo.ToPtr(IncludeNonFailuresFlag),
|
||||||
|
SkipPolicyUpdate: lo.ToPtr(SkipPolicyUpdateFlag),
|
||||||
|
Trace: lo.ToPtr(TraceFlag),
|
||||||
|
PolicyPaths: lo.ToPtr(ConfigPolicyFlag),
|
||||||
|
DataPaths: lo.ToPtr(ConfigDataFlag),
|
||||||
|
PolicyNamespaces: lo.ToPtr(PolicyNamespaceFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MisconfFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.FilePatterns, f.IncludeNonFailures, f.SkipPolicyUpdate, f.Trace, f.PolicyPaths, f.DataPaths, f.PolicyNamespaces}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MisconfFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MisconfFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) {
|
||||||
|
skipPolicyUpdateFlag := getBool(f.SkipPolicyUpdate)
|
||||||
|
if skipPolicyUpdateFlag {
|
||||||
|
log.Logger.Warn("'--skip-policy-update' is no longer necessary as the built-in policies are embedded into the binary")
|
||||||
|
}
|
||||||
|
return MisconfOptions{
|
||||||
|
FilePatterns: getStringSlice(f.FilePatterns),
|
||||||
|
IncludeNonFailures: getBool(f.IncludeNonFailures),
|
||||||
|
Trace: getBool(f.Trace),
|
||||||
|
|
||||||
|
PolicyPaths: getStringSlice(f.PolicyPaths),
|
||||||
|
DataPaths: getStringSlice(f.DataPaths),
|
||||||
|
PolicyNamespaces: getStringSlice(f.PolicyNamespaces),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
297
pkg/flag/options.go
Normal file
297
pkg/flag/options.go
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag struct {
|
||||||
|
// Name is for CLI flag and environment variable.
|
||||||
|
// If this field is empty, it will be available only in config file.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// ConfigName is a key in config file. It is also used as a key of viper.
|
||||||
|
ConfigName string
|
||||||
|
|
||||||
|
// Shorthand is a shorthand letter.
|
||||||
|
Shorthand string
|
||||||
|
|
||||||
|
// Value is the default value. It must be filled to determine the flag type.
|
||||||
|
Value interface{}
|
||||||
|
|
||||||
|
// Usage explains how to use the flag.
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
// Persistent represents if the flag is persistent
|
||||||
|
Persistent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagGroup interface {
|
||||||
|
AddFlags(cmd *cobra.Command)
|
||||||
|
Bind(cmd *cobra.Command) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flags struct {
|
||||||
|
CacheFlagGroup *CacheFlagGroup
|
||||||
|
DBFlagGroup *DBFlagGroup
|
||||||
|
ImageFlagGroup *ImageFlagGroup
|
||||||
|
K8sFlagGroup *K8sFlagGroup
|
||||||
|
MisconfFlagGroup *MisconfFlagGroup
|
||||||
|
RemoteFlagGroup *RemoteFlagGroup
|
||||||
|
ReportFlagGroup *ReportFlagGroup
|
||||||
|
SBOMFlagGroup *SBOMFlagGroup
|
||||||
|
ScanFlagGroup *ScanFlagGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options holds all the runtime configuration
|
||||||
|
type Options struct {
|
||||||
|
GlobalOptions
|
||||||
|
CacheOptions
|
||||||
|
DBOptions
|
||||||
|
ImageOptions
|
||||||
|
K8sOptions
|
||||||
|
MisconfOptions
|
||||||
|
RemoteOptions
|
||||||
|
ReportOptions
|
||||||
|
SBOMOptions
|
||||||
|
ScanOptions
|
||||||
|
|
||||||
|
// Trivy's version, not populated via CLI flags
|
||||||
|
AppVersion string
|
||||||
|
|
||||||
|
// We don't want to allow disabled analyzers to be passed by users, but it is necessary for internal use.
|
||||||
|
DisabledAnalyzers []analyzer.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFlag(cmd *cobra.Command, flag *Flag) {
|
||||||
|
if flag == nil || flag.Name == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v := flag.Value.(type) {
|
||||||
|
case int:
|
||||||
|
if flag.Persistent {
|
||||||
|
cmd.PersistentFlags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
} else {
|
||||||
|
cmd.Flags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
if flag.Persistent {
|
||||||
|
cmd.PersistentFlags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
} else {
|
||||||
|
cmd.Flags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
if flag.Persistent {
|
||||||
|
cmd.PersistentFlags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
} else {
|
||||||
|
cmd.Flags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
if flag.Persistent {
|
||||||
|
cmd.PersistentFlags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
} else {
|
||||||
|
cmd.Flags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
}
|
||||||
|
case time.Duration:
|
||||||
|
if flag.Persistent {
|
||||||
|
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
} else {
|
||||||
|
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bind(cmd *cobra.Command, flag *Flag) error {
|
||||||
|
if flag == nil || flag.Name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if flag.Persistent {
|
||||||
|
if err := viper.BindPFlag(flag.ConfigName, cmd.PersistentFlags().Lookup(flag.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := viper.BindPFlag(flag.ConfigName, cmd.Flags().Lookup(flag.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We don't use viper.AutomaticEnv, so we need to add a prefix manually here.
|
||||||
|
if err := viper.BindEnv(flag.ConfigName, strings.ToUpper("trivy_"+flag.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(flag *Flag) string {
|
||||||
|
if flag == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return viper.GetString(flag.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringSlice(flag *Flag) []string {
|
||||||
|
if flag == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return viper.GetStringSlice(flag.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(flag *Flag) int {
|
||||||
|
if flag == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return viper.GetInt(flag.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBool(flag *Flag) bool {
|
||||||
|
if flag == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return viper.GetBool(flag.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDuration(flag *Flag) time.Duration {
|
||||||
|
if flag == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return viper.GetDuration(flag.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flags) groups() []FlagGroup {
|
||||||
|
var groups []FlagGroup
|
||||||
|
if f.CacheFlagGroup != nil {
|
||||||
|
groups = append(groups, f.CacheFlagGroup)
|
||||||
|
}
|
||||||
|
if f.DBFlagGroup != nil {
|
||||||
|
groups = append(groups, f.DBFlagGroup)
|
||||||
|
}
|
||||||
|
if f.ImageFlagGroup != nil {
|
||||||
|
groups = append(groups, f.ImageFlagGroup)
|
||||||
|
}
|
||||||
|
if f.K8sFlagGroup != nil {
|
||||||
|
groups = append(groups, f.K8sFlagGroup)
|
||||||
|
}
|
||||||
|
if f.MisconfFlagGroup != nil {
|
||||||
|
groups = append(groups, f.MisconfFlagGroup)
|
||||||
|
}
|
||||||
|
if f.RemoteFlagGroup != nil {
|
||||||
|
groups = append(groups, f.RemoteFlagGroup)
|
||||||
|
}
|
||||||
|
if f.ReportFlagGroup != nil {
|
||||||
|
groups = append(groups, f.ReportFlagGroup)
|
||||||
|
}
|
||||||
|
if f.SBOMFlagGroup != nil {
|
||||||
|
groups = append(groups, f.SBOMFlagGroup)
|
||||||
|
}
|
||||||
|
if f.ScanFlagGroup != nil {
|
||||||
|
groups = append(groups, f.ScanFlagGroup)
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flags) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, group := range f.groups() {
|
||||||
|
if group == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
group.AddFlags(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().SetNormalizeFunc(flagNameNormalize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flags) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, group := range f.groups() {
|
||||||
|
if group == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := group.Bind(cmd); err != nil {
|
||||||
|
return xerrors.Errorf("flag groups: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup, output io.Writer) (Options, error) {
|
||||||
|
var err error
|
||||||
|
opts := Options{
|
||||||
|
AppVersion: appVersion,
|
||||||
|
GlobalOptions: globalFlags.ToOptions(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.CacheFlagGroup != nil {
|
||||||
|
opts.CacheOptions, err = f.CacheFlagGroup.ToOptions()
|
||||||
|
if err != nil {
|
||||||
|
return Options{}, xerrors.Errorf("cache flag error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.DBFlagGroup != nil {
|
||||||
|
opts.DBOptions, err = f.DBFlagGroup.ToOptions()
|
||||||
|
if err != nil {
|
||||||
|
return Options{}, xerrors.Errorf("flag error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ImageFlagGroup != nil {
|
||||||
|
opts.ImageOptions = f.ImageFlagGroup.ToOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.K8sFlagGroup != nil {
|
||||||
|
opts.K8sOptions = f.K8sFlagGroup.ToOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.MisconfFlagGroup != nil {
|
||||||
|
opts.MisconfOptions, err = f.MisconfFlagGroup.ToOptions()
|
||||||
|
if err != nil {
|
||||||
|
return Options{}, xerrors.Errorf("misconfiguration flag error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.RemoteFlagGroup != nil {
|
||||||
|
opts.RemoteOptions = f.RemoteFlagGroup.ToOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ReportFlagGroup != nil {
|
||||||
|
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output)
|
||||||
|
if err != nil {
|
||||||
|
return Options{}, xerrors.Errorf("report flag error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.SBOMFlagGroup != nil {
|
||||||
|
opts.SBOMOptions, err = f.SBOMFlagGroup.ToOptions()
|
||||||
|
if err != nil {
|
||||||
|
return Options{}, xerrors.Errorf("sbom flag error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ScanFlagGroup != nil {
|
||||||
|
opts.ScanOptions = f.ScanFlagGroup.ToOptions(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagNameNormalize(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
switch name {
|
||||||
|
case "skip-update":
|
||||||
|
name = SkipDBUpdateFlag.Name
|
||||||
|
case "policy":
|
||||||
|
name = ConfigPolicyFlag.Name
|
||||||
|
case "data":
|
||||||
|
name = ConfigDataFlag.Name
|
||||||
|
case "namespaces":
|
||||||
|
name = PolicyNamespaceFlag.Name
|
||||||
|
case "ctx":
|
||||||
|
name = ClusterContextFlag.Name
|
||||||
|
}
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
155
pkg/flag/remote_flags.go
Normal file
155
pkg/flag/remote_flags.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultTokenHeader = "Trivy-Token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ServerTokenFlag = Flag{
|
||||||
|
Name: "token",
|
||||||
|
ConfigName: "server.token",
|
||||||
|
Value: "",
|
||||||
|
Usage: "for authentication in client/server mode",
|
||||||
|
}
|
||||||
|
ServerTokenHeaderFlag = Flag{
|
||||||
|
Name: "token-header",
|
||||||
|
ConfigName: "server.token-header",
|
||||||
|
Value: DefaultTokenHeader,
|
||||||
|
Usage: "specify a header name for token in client/server mode",
|
||||||
|
}
|
||||||
|
ServerAddrFlag = Flag{
|
||||||
|
Name: "server",
|
||||||
|
ConfigName: "server.addr",
|
||||||
|
Value: "",
|
||||||
|
Usage: "server address in client mode",
|
||||||
|
}
|
||||||
|
ServerCustomHeadersFlag = Flag{
|
||||||
|
Name: "custom-headers",
|
||||||
|
ConfigName: "server.custom-headers",
|
||||||
|
Value: []string{},
|
||||||
|
Usage: "custom headers in client mode",
|
||||||
|
}
|
||||||
|
ServerListenFlag = Flag{
|
||||||
|
Name: "listen",
|
||||||
|
ConfigName: "server.listen",
|
||||||
|
Value: "localhost:4954",
|
||||||
|
Usage: "listen address in server mode",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoteFlagGroup composes common printer flag structs
|
||||||
|
// used for commands requiring reporting logic.
|
||||||
|
type RemoteFlagGroup struct {
|
||||||
|
// for client/server
|
||||||
|
Token *Flag
|
||||||
|
TokenHeader *Flag
|
||||||
|
|
||||||
|
// for client
|
||||||
|
ServerAddr *Flag
|
||||||
|
CustomHeaders *Flag
|
||||||
|
|
||||||
|
// for server
|
||||||
|
Listen *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteOptions struct {
|
||||||
|
Token string
|
||||||
|
TokenHeader string
|
||||||
|
|
||||||
|
ServerAddr string
|
||||||
|
Listen string
|
||||||
|
CustomHeaders http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientFlags() *RemoteFlagGroup {
|
||||||
|
return &RemoteFlagGroup{
|
||||||
|
Token: &ServerTokenFlag,
|
||||||
|
TokenHeader: &ServerTokenHeaderFlag,
|
||||||
|
ServerAddr: &ServerAddrFlag,
|
||||||
|
CustomHeaders: &ServerCustomHeadersFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerFlags() *RemoteFlagGroup {
|
||||||
|
return &RemoteFlagGroup{
|
||||||
|
Token: &ServerTokenFlag,
|
||||||
|
TokenHeader: &ServerTokenHeaderFlag,
|
||||||
|
Listen: &ServerListenFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RemoteFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.Token, f.TokenHeader, f.ServerAddr, f.CustomHeaders, f.Listen}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RemoteFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RemoteFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RemoteFlagGroup) ToOptions() RemoteOptions {
|
||||||
|
serverAddr := getString(f.ServerAddr)
|
||||||
|
customHeaders := splitCustomHeaders(getStringSlice(f.CustomHeaders))
|
||||||
|
listen := getString(f.Listen)
|
||||||
|
token := getString(f.Token)
|
||||||
|
tokenHeader := getString(f.TokenHeader)
|
||||||
|
|
||||||
|
if serverAddr == "" && listen == "" {
|
||||||
|
switch {
|
||||||
|
case len(customHeaders) > 0:
|
||||||
|
log.Logger.Warn(`"--custom-header" can be used only with "--server"`)
|
||||||
|
case token != "":
|
||||||
|
log.Logger.Warn(`"--token" can be used only with "--server"`)
|
||||||
|
case tokenHeader != "" && tokenHeader != DefaultTokenHeader:
|
||||||
|
log.Logger.Warn(`"--token-header" can be used only with "--server"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == "" && tokenHeader != DefaultTokenHeader {
|
||||||
|
log.Logger.Warn(`"--token-header" should be used with "--token"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != "" && tokenHeader != "" {
|
||||||
|
customHeaders.Set(tokenHeader, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemoteOptions{
|
||||||
|
Token: token,
|
||||||
|
TokenHeader: tokenHeader,
|
||||||
|
ServerAddr: serverAddr,
|
||||||
|
CustomHeaders: customHeaders,
|
||||||
|
Listen: listen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitCustomHeaders(headers []string) http.Header {
|
||||||
|
result := make(http.Header)
|
||||||
|
for _, header := range headers {
|
||||||
|
// e.g. x-api-token:XXX
|
||||||
|
s := strings.SplitN(header, ":", 2)
|
||||||
|
if len(s) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.Set(s[0], s[1])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
127
pkg/flag/remote_flags_test.go
Normal file
127
pkg/flag/remote_flags_test.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoteFlagGroup_ToOptions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Server string
|
||||||
|
CustomHeaders []string
|
||||||
|
Token string
|
||||||
|
TokenHeader string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want flag.RemoteOptions
|
||||||
|
wantLogs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy",
|
||||||
|
fields: fields{
|
||||||
|
Server: "http://localhost:4954",
|
||||||
|
CustomHeaders: []string{
|
||||||
|
"x-api-token:foo bar",
|
||||||
|
"Authorization:user:password",
|
||||||
|
},
|
||||||
|
Token: "token",
|
||||||
|
TokenHeader: "Trivy-Token",
|
||||||
|
},
|
||||||
|
want: flag.RemoteOptions{
|
||||||
|
ServerAddr: "http://localhost:4954",
|
||||||
|
CustomHeaders: http.Header{
|
||||||
|
"X-Api-Token": []string{"foo bar"},
|
||||||
|
"Authorization": []string{"user:password"},
|
||||||
|
"Trivy-Token": []string{"token"},
|
||||||
|
},
|
||||||
|
Token: "token",
|
||||||
|
TokenHeader: "Trivy-Token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom headers and no server",
|
||||||
|
fields: fields{
|
||||||
|
CustomHeaders: []string{
|
||||||
|
"Authorization:user:password",
|
||||||
|
},
|
||||||
|
TokenHeader: "Trivy-Token",
|
||||||
|
},
|
||||||
|
want: flag.RemoteOptions{
|
||||||
|
CustomHeaders: http.Header{
|
||||||
|
"Authorization": []string{"user:password"},
|
||||||
|
},
|
||||||
|
TokenHeader: "Trivy-Token",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`"--custom-header" can be used only with "--server"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token and no server",
|
||||||
|
fields: fields{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
|
want: flag.RemoteOptions{
|
||||||
|
CustomHeaders: http.Header{},
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`"--token" can be used only with "--server"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token header and no token",
|
||||||
|
fields: fields{
|
||||||
|
Server: "http://localhost:4954",
|
||||||
|
TokenHeader: "Non-Default",
|
||||||
|
},
|
||||||
|
want: flag.RemoteOptions{
|
||||||
|
CustomHeaders: http.Header{},
|
||||||
|
ServerAddr: "http://localhost:4954",
|
||||||
|
TokenHeader: "Non-Default",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`"--token-header" should be used with "--token"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
level := zap.WarnLevel
|
||||||
|
core, obs := observer.New(level)
|
||||||
|
log.Logger = zap.New(core).Sugar()
|
||||||
|
|
||||||
|
viper.Set(flag.ServerAddrFlag.ConfigName, tt.fields.Server)
|
||||||
|
viper.Set(flag.ServerCustomHeadersFlag.ConfigName, tt.fields.CustomHeaders)
|
||||||
|
viper.Set(flag.ServerTokenFlag.ConfigName, tt.fields.Token)
|
||||||
|
viper.Set(flag.ServerTokenHeaderFlag.ConfigName, tt.fields.TokenHeader)
|
||||||
|
|
||||||
|
// Assert options
|
||||||
|
f := &flag.RemoteFlagGroup{
|
||||||
|
ServerAddr: &flag.ServerAddrFlag,
|
||||||
|
CustomHeaders: &flag.ServerCustomHeadersFlag,
|
||||||
|
Token: &flag.ServerTokenFlag,
|
||||||
|
TokenHeader: &flag.ServerTokenHeaderFlag,
|
||||||
|
}
|
||||||
|
got := f.ToOptions()
|
||||||
|
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||||
|
|
||||||
|
// Assert log messages
|
||||||
|
var gotMessages []string
|
||||||
|
for _, entry := range obs.AllUntimed() {
|
||||||
|
gotMessages = append(gotMessages, entry.Message)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
251
pkg/flag/report_flags.go
Normal file
251
pkg/flag/report_flags.go
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// e.g. config yaml
|
||||||
|
// report:
|
||||||
|
// format: table
|
||||||
|
// dependency-tree: true
|
||||||
|
// exit-code: 1
|
||||||
|
// severity: HIGH,CRITICAL
|
||||||
|
var (
|
||||||
|
FormatFlag = Flag{
|
||||||
|
Name: "format",
|
||||||
|
ConfigName: "format",
|
||||||
|
Shorthand: "f",
|
||||||
|
Value: report.FormatTable,
|
||||||
|
Usage: "format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github)",
|
||||||
|
}
|
||||||
|
ReportFormatFlag = Flag{
|
||||||
|
Name: "report",
|
||||||
|
ConfigName: "report",
|
||||||
|
Value: "all",
|
||||||
|
Usage: "specify a report format for the output. (all,summary)",
|
||||||
|
}
|
||||||
|
TemplateFlag = Flag{
|
||||||
|
Name: "template",
|
||||||
|
ConfigName: "template",
|
||||||
|
Shorthand: "t",
|
||||||
|
Value: "",
|
||||||
|
Usage: "output template",
|
||||||
|
}
|
||||||
|
DependencyTreeFlag = Flag{
|
||||||
|
Name: "dependency-tree",
|
||||||
|
ConfigName: "dependency-tree",
|
||||||
|
Value: false,
|
||||||
|
Usage: "show dependency origin tree (EXPERIMENTAL)",
|
||||||
|
}
|
||||||
|
ListAllPkgsFlag = Flag{
|
||||||
|
Name: "list-all-pkgs",
|
||||||
|
ConfigName: "list-all-pkgs",
|
||||||
|
Value: false,
|
||||||
|
Usage: "enabling the option will output all packages regardless of vulnerability",
|
||||||
|
}
|
||||||
|
IgnoreFileFlag = Flag{
|
||||||
|
Name: "ignorefile",
|
||||||
|
ConfigName: "ignorefile",
|
||||||
|
Value: result.DefaultIgnoreFile,
|
||||||
|
Usage: "specify .trivyignore file",
|
||||||
|
}
|
||||||
|
IgnorePolicyFlag = Flag{
|
||||||
|
Name: "ignore-policy",
|
||||||
|
ConfigName: "ignore-policy",
|
||||||
|
Value: "",
|
||||||
|
Usage: "specify the Rego file path to evaluate each vulnerability",
|
||||||
|
}
|
||||||
|
ExitCodeFlag = Flag{
|
||||||
|
Name: "exit-code",
|
||||||
|
ConfigName: "exit-code",
|
||||||
|
Value: 0,
|
||||||
|
Usage: "specify exit code when any security issues are found",
|
||||||
|
}
|
||||||
|
OutputFlag = Flag{
|
||||||
|
Name: "output",
|
||||||
|
ConfigName: "output",
|
||||||
|
Shorthand: "o",
|
||||||
|
Value: "",
|
||||||
|
Usage: "output file name",
|
||||||
|
}
|
||||||
|
SeverityFlag = Flag{
|
||||||
|
Name: "severity",
|
||||||
|
ConfigName: "severity",
|
||||||
|
Shorthand: "s",
|
||||||
|
Value: strings.Join(dbTypes.SeverityNames, ","),
|
||||||
|
Usage: "severities of security issues to be displayed (comma separated)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vulnerabilities
|
||||||
|
IgnoreUnfixedFlag = Flag{
|
||||||
|
Name: "ignore-unfixed",
|
||||||
|
ConfigName: "vulnerability.ignore-unfixed",
|
||||||
|
Value: false,
|
||||||
|
Usage: "display only fixed vulnerabilities",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportFlagGroup composes common printer flag structs
|
||||||
|
// used for commands requiring reporting logic.
|
||||||
|
type ReportFlagGroup struct {
|
||||||
|
Format *Flag
|
||||||
|
ReportFormat *Flag
|
||||||
|
Template *Flag
|
||||||
|
DependencyTree *Flag
|
||||||
|
ListAllPkgs *Flag
|
||||||
|
IgnoreUnfixed *Flag
|
||||||
|
IgnoreFile *Flag
|
||||||
|
IgnorePolicy *Flag
|
||||||
|
ExitCode *Flag
|
||||||
|
Output *Flag
|
||||||
|
Severity *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportOptions struct {
|
||||||
|
Format string
|
||||||
|
ReportFormat string
|
||||||
|
Template string
|
||||||
|
DependencyTree bool
|
||||||
|
ListAllPkgs bool
|
||||||
|
IgnoreUnfixed bool
|
||||||
|
IgnoreFile string
|
||||||
|
ExitCode int
|
||||||
|
IgnorePolicy string
|
||||||
|
Output io.Writer
|
||||||
|
Severities []dbTypes.Severity
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReportFlagGroup() *ReportFlagGroup {
|
||||||
|
return &ReportFlagGroup{
|
||||||
|
Format: lo.ToPtr(FormatFlag),
|
||||||
|
ReportFormat: lo.ToPtr(ReportFormatFlag),
|
||||||
|
Template: lo.ToPtr(TemplateFlag),
|
||||||
|
DependencyTree: lo.ToPtr(DependencyTreeFlag),
|
||||||
|
ListAllPkgs: lo.ToPtr(ListAllPkgsFlag),
|
||||||
|
IgnoreUnfixed: lo.ToPtr(IgnoreUnfixedFlag),
|
||||||
|
IgnoreFile: lo.ToPtr(IgnoreFileFlag),
|
||||||
|
IgnorePolicy: lo.ToPtr(IgnorePolicyFlag),
|
||||||
|
ExitCode: lo.ToPtr(ExitCodeFlag),
|
||||||
|
Output: lo.ToPtr(OutputFlag),
|
||||||
|
Severity: lo.ToPtr(SeverityFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReportFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreUnfixed, f.IgnoreFile, f.IgnorePolicy,
|
||||||
|
f.ExitCode, f.Output, f.Severity}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReportFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReportFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
|
||||||
|
format := getString(f.Format)
|
||||||
|
template := getString(f.Template)
|
||||||
|
dependencyTree := getBool(f.DependencyTree)
|
||||||
|
listAllPkgs := getBool(f.ListAllPkgs)
|
||||||
|
output := getString(f.Output)
|
||||||
|
|
||||||
|
if template != "" {
|
||||||
|
if format == "" {
|
||||||
|
log.Logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
|
||||||
|
} else if format != "template" {
|
||||||
|
log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if format == report.FormatTemplate {
|
||||||
|
log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "--list-all-pkgs" option is unavailable with "--format table".
|
||||||
|
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
|
||||||
|
if listAllPkgs && format == report.FormatTable {
|
||||||
|
log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "--dependency-tree" option is available only with "--format table".
|
||||||
|
if dependencyTree && format != report.FormatTable {
|
||||||
|
log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable '--list-all-pkgs' if needed
|
||||||
|
if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) {
|
||||||
|
listAllPkgs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
var err error
|
||||||
|
if out, err = os.Create(output); err != nil {
|
||||||
|
return ReportOptions{}, xerrors.Errorf("failed to create an output file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReportOptions{
|
||||||
|
Format: format,
|
||||||
|
ReportFormat: getString(f.ReportFormat),
|
||||||
|
Template: template,
|
||||||
|
DependencyTree: dependencyTree,
|
||||||
|
ListAllPkgs: listAllPkgs,
|
||||||
|
IgnoreUnfixed: getBool(f.IgnoreUnfixed),
|
||||||
|
IgnoreFile: getString(f.IgnoreFile),
|
||||||
|
ExitCode: getInt(f.ExitCode),
|
||||||
|
IgnorePolicy: getString(f.IgnorePolicy),
|
||||||
|
Output: out,
|
||||||
|
Severities: splitSeverity(getString(f.Severity)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {
|
||||||
|
if slices.Contains(report.SupportedSBOMFormats, format) && !listAllPkgs {
|
||||||
|
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", report.SupportedSBOMFormats)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if dependencyTree && !listAllPkgs {
|
||||||
|
log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitSeverity(severity string) []dbTypes.Severity {
|
||||||
|
if severity == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var severities []dbTypes.Severity
|
||||||
|
for _, s := range strings.Split(severity, ",") {
|
||||||
|
sev, err := dbTypes.NewSeverity(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Logger.Warnf("unknown severity option: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
severities = append(severities, sev)
|
||||||
|
}
|
||||||
|
log.Logger.Debugf("Severities: %q", severities)
|
||||||
|
return severities
|
||||||
|
}
|
||||||
208
pkg/flag/report_flags_test.go
Normal file
208
pkg/flag/report_flags_test.go
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReportFlagGroup_ToOptions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
format string
|
||||||
|
template string
|
||||||
|
dependencyTree bool
|
||||||
|
listAllPkgs bool
|
||||||
|
ignoreUnfixed bool
|
||||||
|
ignoreFile string
|
||||||
|
exitCode int
|
||||||
|
ignorePolicy string
|
||||||
|
output string
|
||||||
|
severities string
|
||||||
|
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want flag.ReportOptions
|
||||||
|
wantLogs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy default (without flags)",
|
||||||
|
fields: fields{},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path with an unknown severity",
|
||||||
|
fields: fields{
|
||||||
|
severities: "CRITICAL,INVALID",
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Severities: []dbTypes.Severity{
|
||||||
|
dbTypes.SeverityCritical,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
"unknown severity option: unknown severity: INVALID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path with an cyclonedx",
|
||||||
|
fields: fields{
|
||||||
|
severities: "CRITICAL",
|
||||||
|
format: "cyclonedx",
|
||||||
|
listAllPkgs: true,
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||||
|
Format: report.FormatCycloneDX,
|
||||||
|
ListAllPkgs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path with an cyclonedx option list-all-pkgs is false",
|
||||||
|
fields: fields{
|
||||||
|
severities: "CRITICAL",
|
||||||
|
format: "cyclonedx",
|
||||||
|
listAllPkgs: false,
|
||||||
|
|
||||||
|
debug: true,
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`["cyclonedx" "spdx" "spdx-json" "github"] automatically enables '--list-all-pkgs'.`,
|
||||||
|
`Severities: ["CRITICAL"]`,
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Severities: []dbTypes.Severity{
|
||||||
|
dbTypes.SeverityCritical,
|
||||||
|
},
|
||||||
|
Format: report.FormatCycloneDX,
|
||||||
|
ListAllPkgs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid option combination: --template enabled without --format",
|
||||||
|
fields: fields{
|
||||||
|
template: "@contrib/gitlab.tpl",
|
||||||
|
severities: "LOW",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||||
|
Template: "@contrib/gitlab.tpl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid option combination: --template and --format json",
|
||||||
|
fields: fields{
|
||||||
|
format: "json",
|
||||||
|
template: "@contrib/gitlab.tpl",
|
||||||
|
severities: "LOW",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Format: "json",
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||||
|
Template: "@contrib/gitlab.tpl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid option combination: --format template without --template",
|
||||||
|
fields: fields{
|
||||||
|
format: "template",
|
||||||
|
severities: "LOW",
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Format: "template",
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid option combination: --list-all-pkgs with --format table",
|
||||||
|
fields: fields{
|
||||||
|
format: "table",
|
||||||
|
severities: "LOW",
|
||||||
|
listAllPkgs: true,
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`,
|
||||||
|
},
|
||||||
|
want: flag.ReportOptions{
|
||||||
|
Format: "table",
|
||||||
|
Output: os.Stdout,
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||||
|
ListAllPkgs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
level := zap.WarnLevel
|
||||||
|
if tt.fields.debug {
|
||||||
|
level = zap.DebugLevel
|
||||||
|
}
|
||||||
|
core, obs := observer.New(level)
|
||||||
|
log.Logger = zap.New(core).Sugar()
|
||||||
|
|
||||||
|
viper.Set(flag.FormatFlag.ConfigName, tt.fields.format)
|
||||||
|
viper.Set(flag.TemplateFlag.ConfigName, tt.fields.template)
|
||||||
|
viper.Set(flag.DependencyTreeFlag.ConfigName, tt.fields.dependencyTree)
|
||||||
|
viper.Set(flag.ListAllPkgsFlag.ConfigName, tt.fields.listAllPkgs)
|
||||||
|
viper.Set(flag.IgnoreFileFlag.ConfigName, tt.fields.ignoreFile)
|
||||||
|
viper.Set(flag.IgnoreUnfixedFlag.ConfigName, tt.fields.ignoreUnfixed)
|
||||||
|
viper.Set(flag.IgnorePolicyFlag.ConfigName, tt.fields.ignorePolicy)
|
||||||
|
viper.Set(flag.ExitCodeFlag.ConfigName, tt.fields.exitCode)
|
||||||
|
viper.Set(flag.OutputFlag.ConfigName, tt.fields.output)
|
||||||
|
viper.Set(flag.SeverityFlag.ConfigName, tt.fields.severities)
|
||||||
|
|
||||||
|
// Assert options
|
||||||
|
f := &flag.ReportFlagGroup{
|
||||||
|
Format: &flag.FormatFlag,
|
||||||
|
Template: &flag.TemplateFlag,
|
||||||
|
DependencyTree: &flag.DependencyTreeFlag,
|
||||||
|
ListAllPkgs: &flag.ListAllPkgsFlag,
|
||||||
|
IgnoreFile: &flag.IgnoreFileFlag,
|
||||||
|
IgnoreUnfixed: &flag.IgnoreUnfixedFlag,
|
||||||
|
IgnorePolicy: &flag.IgnorePolicyFlag,
|
||||||
|
ExitCode: &flag.ExitCodeFlag,
|
||||||
|
Output: &flag.OutputFlag,
|
||||||
|
Severity: &flag.SeverityFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := f.ToOptions(os.Stdout)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||||
|
|
||||||
|
// Assert log messages
|
||||||
|
var gotMessages []string
|
||||||
|
for _, entry := range obs.AllUntimed() {
|
||||||
|
gotMessages = append(gotMessages, entry.Message)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
67
pkg/flag/sbom_flags.go
Normal file
67
pkg/flag/sbom_flags.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ArtifactTypeFlag = Flag{
|
||||||
|
Name: "artifact-type",
|
||||||
|
Value: "",
|
||||||
|
Usage: "deprecated",
|
||||||
|
}
|
||||||
|
SBOMFormatFlag = Flag{
|
||||||
|
Name: "sbom-format",
|
||||||
|
Value: "",
|
||||||
|
Usage: "deprecated",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type SBOMFlagGroup struct {
|
||||||
|
ArtifactType *Flag // deprecated
|
||||||
|
SBOMFormat *Flag // deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
type SBOMOptions struct {
|
||||||
|
ArtifactType string // deprecated
|
||||||
|
SBOMFormat string // deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSBOMFlagGroup() *SBOMFlagGroup {
|
||||||
|
return &SBOMFlagGroup{
|
||||||
|
ArtifactType: &ArtifactTypeFlag,
|
||||||
|
SBOMFormat: &SBOMFormatFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SBOMFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
if f.ArtifactType != nil {
|
||||||
|
cmd.Flags().String(ArtifactTypeFlag.Name, "", "deprecated")
|
||||||
|
cmd.Flags().MarkHidden(ArtifactTypeFlag.Name) // nolint: gosec
|
||||||
|
}
|
||||||
|
if f.SBOMFormat != nil {
|
||||||
|
cmd.Flags().String(SBOMFormatFlag.Name, "", "deprecated")
|
||||||
|
cmd.Flags().MarkHidden(SBOMFormatFlag.Name) // nolint: gosec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SBOMFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
// All the flags are deprecated
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) {
|
||||||
|
artifactType := getString(f.ArtifactType)
|
||||||
|
sbomFormat := getString(f.SBOMFormat)
|
||||||
|
|
||||||
|
if artifactType != "" || sbomFormat != "" {
|
||||||
|
log.Logger.Error("'trivy sbom' is now for scanning SBOM. " +
|
||||||
|
"See https://github.com/aquasecurity/trivy/discussions/2407 for the detail")
|
||||||
|
return SBOMOptions{}, xerrors.New("'--artifact-type' and '--sbom-format' are no longer available")
|
||||||
|
}
|
||||||
|
|
||||||
|
return SBOMOptions{}, nil
|
||||||
|
}
|
||||||
161
pkg/flag/scan_flags.go
Normal file
161
pkg/flag/scan_flags.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SkipDirsFlag = Flag{
|
||||||
|
Name: "skip-dirs",
|
||||||
|
ConfigName: "scan.skip-dirs",
|
||||||
|
Value: "",
|
||||||
|
Usage: "specify the directories where the traversal is skipped",
|
||||||
|
}
|
||||||
|
SkipFilesFlag = Flag{
|
||||||
|
Name: "skip-files",
|
||||||
|
ConfigName: "scan.skip-files",
|
||||||
|
Value: "",
|
||||||
|
Usage: "specify the file paths to skip traversal",
|
||||||
|
}
|
||||||
|
OfflineScanFlag = Flag{
|
||||||
|
Name: "offline-scan",
|
||||||
|
ConfigName: "scan.offline",
|
||||||
|
Value: false,
|
||||||
|
Usage: "do not issue API requests to identify dependencies",
|
||||||
|
}
|
||||||
|
SecurityChecksFlag = Flag{
|
||||||
|
Name: "security-checks",
|
||||||
|
ConfigName: "scan.security-checks",
|
||||||
|
Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret),
|
||||||
|
Usage: "comma-separated list of what security issues to detect (vuln,config,secret)",
|
||||||
|
}
|
||||||
|
VulnTypeFlag = Flag{
|
||||||
|
Name: "vuln-type",
|
||||||
|
ConfigName: "vulnerability.type",
|
||||||
|
Value: strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","),
|
||||||
|
Usage: "comma-separated list of vulnerability types (os,library)",
|
||||||
|
}
|
||||||
|
SecretConfigFlag = Flag{
|
||||||
|
Name: "secret-config",
|
||||||
|
ConfigName: "secret.config",
|
||||||
|
Value: "trivy-secret.yaml",
|
||||||
|
Usage: "specify a path to config file for secret scanning",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScanFlagGroup struct {
|
||||||
|
SkipDirs *Flag
|
||||||
|
SkipFiles *Flag
|
||||||
|
OfflineScan *Flag
|
||||||
|
SecurityChecks *Flag
|
||||||
|
|
||||||
|
VulnType *Flag
|
||||||
|
SecretConfig *Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScanOptions struct {
|
||||||
|
Target string
|
||||||
|
SkipDirs []string
|
||||||
|
SkipFiles []string
|
||||||
|
OfflineScan bool
|
||||||
|
SecurityChecks []string
|
||||||
|
|
||||||
|
// Vulnerabilities
|
||||||
|
VulnType []string
|
||||||
|
|
||||||
|
// Secrets
|
||||||
|
SecretConfigPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScanFlagGroup() *ScanFlagGroup {
|
||||||
|
return &ScanFlagGroup{
|
||||||
|
SkipDirs: lo.ToPtr(SkipDirsFlag),
|
||||||
|
SkipFiles: lo.ToPtr(SkipFilesFlag),
|
||||||
|
OfflineScan: lo.ToPtr(OfflineScanFlag),
|
||||||
|
SecurityChecks: lo.ToPtr(SecurityChecksFlag),
|
||||||
|
VulnType: lo.ToPtr(VulnTypeFlag),
|
||||||
|
SecretConfig: lo.ToPtr(SecretConfigFlag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ScanFlagGroup) flags() []*Flag {
|
||||||
|
return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks, f.VulnType, f.SecretConfig}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ScanFlagGroup) Bind(cmd *cobra.Command) error {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
if err := bind(cmd, flag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ScanFlagGroup) AddFlags(cmd *cobra.Command) {
|
||||||
|
for _, flag := range f.flags() {
|
||||||
|
addFlag(cmd, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ScanFlagGroup) ToOptions(args []string) ScanOptions {
|
||||||
|
var target string
|
||||||
|
if len(args) == 1 {
|
||||||
|
target = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanOptions{
|
||||||
|
Target: target,
|
||||||
|
SkipDirs: getStringSlice(f.SkipDirs),
|
||||||
|
SkipFiles: getStringSlice(f.SkipFiles),
|
||||||
|
OfflineScan: getBool(f.OfflineScan),
|
||||||
|
VulnType: parseVulnType(getStringSlice(f.VulnType)),
|
||||||
|
SecurityChecks: parseSecurityCheck(getStringSlice(f.SecurityChecks)),
|
||||||
|
SecretConfigPath: getString(f.SecretConfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVulnType(vulnType []string) []string {
|
||||||
|
switch {
|
||||||
|
case len(vulnType) == 0: // no types
|
||||||
|
return nil
|
||||||
|
case len(vulnType) == 1 && strings.Contains(vulnType[0], ","): // get checks from flag
|
||||||
|
vulnType = strings.Split(vulnType[0], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var vulnTypes []string
|
||||||
|
for _, v := range vulnType {
|
||||||
|
if !slices.Contains(types.VulnTypes, v) {
|
||||||
|
log.Logger.Warnf("unknown vulnerability type: %s", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vulnTypes = append(vulnTypes, v)
|
||||||
|
}
|
||||||
|
return vulnTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSecurityCheck(securityCheck []string) []string {
|
||||||
|
switch {
|
||||||
|
case len(securityCheck) == 0: // no checks
|
||||||
|
return nil
|
||||||
|
case len(securityCheck) == 1 && strings.Contains(securityCheck[0], ","): // get checks from flag
|
||||||
|
securityCheck = strings.Split(securityCheck[0], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var securityChecks []string
|
||||||
|
for _, v := range securityCheck {
|
||||||
|
if !slices.Contains(types.SecurityChecks, v) {
|
||||||
|
log.Logger.Warnf("unknown security check: %s", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
securityChecks = append(securityChecks, v)
|
||||||
|
}
|
||||||
|
return securityChecks
|
||||||
|
}
|
||||||
175
pkg/flag/scan_flags_test.go
Normal file
175
pkg/flag/scan_flags_test.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScanFlagGroup_ToOptions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
skipDirs []string
|
||||||
|
skipFiles []string
|
||||||
|
offlineScan bool
|
||||||
|
vulnType string
|
||||||
|
securityChecks string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
fields fields
|
||||||
|
want flag.ScanOptions
|
||||||
|
wantLogs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
args: []string{"alpine:latest"},
|
||||||
|
fields: fields{},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
Target: "alpine:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path for OS vulnerabilities",
|
||||||
|
args: []string{"alpine:latest"},
|
||||||
|
fields: fields{
|
||||||
|
vulnType: "os",
|
||||||
|
securityChecks: "vuln",
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
Target: "alpine:latest",
|
||||||
|
VulnType: []string{types.VulnTypeOS},
|
||||||
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path for library vulnerabilities",
|
||||||
|
args: []string{"alpine:latest"},
|
||||||
|
fields: fields{
|
||||||
|
vulnType: "library",
|
||||||
|
securityChecks: "vuln",
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
Target: "alpine:latest",
|
||||||
|
VulnType: []string{types.VulnTypeLibrary},
|
||||||
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy path for configs",
|
||||||
|
args: []string{"alpine:latest"},
|
||||||
|
fields: fields{
|
||||||
|
securityChecks: "config",
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
Target: "alpine:latest",
|
||||||
|
SecurityChecks: []string{types.SecurityCheckConfig},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with wrong security check",
|
||||||
|
fields: fields{
|
||||||
|
securityChecks: "vuln,WRONG-CHECK",
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`unknown security check: WRONG-CHECK`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with wrong vuln type",
|
||||||
|
fields: fields{
|
||||||
|
vulnType: "os,nonevuln",
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
VulnType: []string{types.VulnTypeOS},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`unknown vulnerability type: nonevuln`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without target (args)",
|
||||||
|
args: []string{},
|
||||||
|
fields: fields{},
|
||||||
|
want: flag.ScanOptions{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with two or more targets (args)",
|
||||||
|
args: []string{"alpine:latest", "nginx:latest"},
|
||||||
|
fields: fields{},
|
||||||
|
want: flag.ScanOptions{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skip two files",
|
||||||
|
fields: fields{
|
||||||
|
skipFiles: []string{"file1", "file2"},
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
SkipFiles: []string{"file1", "file2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skip two folders",
|
||||||
|
fields: fields{
|
||||||
|
skipDirs: []string{"dir1", "dir2"},
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
SkipDirs: []string{"dir1", "dir2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "offline scan",
|
||||||
|
fields: fields{
|
||||||
|
offlineScan: true,
|
||||||
|
},
|
||||||
|
want: flag.ScanOptions{
|
||||||
|
OfflineScan: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
level := zap.WarnLevel
|
||||||
|
|
||||||
|
core, obs := observer.New(level)
|
||||||
|
log.Logger = zap.New(core).Sugar()
|
||||||
|
|
||||||
|
viper.Set(flag.SkipDirsFlag.ConfigName, tt.fields.skipDirs)
|
||||||
|
viper.Set(flag.SkipFilesFlag.ConfigName, tt.fields.skipFiles)
|
||||||
|
viper.Set(flag.OfflineScanFlag.ConfigName, tt.fields.offlineScan)
|
||||||
|
viper.Set(flag.VulnTypeFlag.ConfigName, tt.fields.vulnType)
|
||||||
|
viper.Set(flag.SecurityChecksFlag.ConfigName, tt.fields.securityChecks)
|
||||||
|
|
||||||
|
// Assert options
|
||||||
|
f := &flag.ScanFlagGroup{
|
||||||
|
SkipDirs: &flag.SkipDirsFlag,
|
||||||
|
SkipFiles: &flag.SkipFilesFlag,
|
||||||
|
OfflineScan: &flag.OfflineScanFlag,
|
||||||
|
VulnType: &flag.VulnTypeFlag,
|
||||||
|
SecurityChecks: &flag.SecurityChecksFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := f.ToOptions(tt.args)
|
||||||
|
assert.Equalf(t, tt.want, got, "ToOptions()")
|
||||||
|
|
||||||
|
// Assert log messages
|
||||||
|
var gotMessages []string
|
||||||
|
for _, entry := range obs.AllUntimed() {
|
||||||
|
gotMessages = append(gotMessages, entry.Message)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clusterRun runs scan on kubernetes cluster
|
// clusterRun runs scan on kubernetes cluster
|
||||||
func clusterRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
|
func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error {
|
||||||
if err := validateReportArguments(cliCtx); err != nil {
|
if err := validateReportArguments(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts, err := trivyk8s.New(cluster, log.Logger).ListArtifacts(cliCtx.Context)
|
artifacts, err := trivyk8s.New(cluster, log.Logger).ListArtifacts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), artifacts)
|
return run(ctx, opts, cluster.GetCurrentContext(), artifacts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"context"
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
"golang.org/x/xerrors"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// namespaceRun runs scan on kubernetes cluster
|
// namespaceRun runs scan on kubernetes cluster
|
||||||
func namespaceRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
|
func namespaceRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error {
|
||||||
if err := validateReportArguments(cliCtx); err != nil {
|
if err := validateReportArguments(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opt, cluster.GetCurrentNamespace()))
|
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opts, cluster.GetCurrentNamespace()))
|
||||||
|
|
||||||
artifacts, err := trivyk8s.ListArtifacts(cliCtx.Context)
|
artifacts, err := trivyk8s.ListArtifacts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), artifacts)
|
return run(ctx, opts, cluster.GetCurrentContext(), artifacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNamespace(opt cmd.Option, currentNamespace string) string {
|
func getNamespace(opts flag.Options, currentNamespace string) string {
|
||||||
if len(opt.KubernetesOption.Namespace) > 0 {
|
if len(opts.K8sOptions.Namespace) > 0 {
|
||||||
return opt.KubernetesOption.Namespace
|
return opts.K8sOptions.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentNamespace
|
return currentNamespace
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
"gotest.tools/assert"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_getNamespace(t *testing.T) {
|
func Test_getNamespace(t *testing.T) {
|
||||||
@@ -13,27 +13,35 @@ func Test_getNamespace(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
currentNamespace string
|
currentNamespace string
|
||||||
opt cmd.Option
|
opts flag.Options
|
||||||
expected string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "--namespace=custom",
|
name: "--namespace=custom",
|
||||||
currentNamespace: "default",
|
currentNamespace: "default",
|
||||||
opt: cmd.Option{KubernetesOption: option.KubernetesOption{Namespace: "custom"}},
|
opts: flag.Options{
|
||||||
expected: "custom",
|
K8sOptions: flag.K8sOptions{
|
||||||
|
Namespace: "custom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "custom",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no namespaces passed",
|
name: "no namespaces passed",
|
||||||
currentNamespace: "default",
|
currentNamespace: "default",
|
||||||
opt: cmd.Option{KubernetesOption: option.KubernetesOption{Namespace: ""}},
|
opts: flag.Options{
|
||||||
expected: "default",
|
K8sOptions: flag.K8sOptions{
|
||||||
|
Namespace: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "default",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
got := getNamespace(test.opt, test.currentNamespace)
|
got := getNamespace(test.opts, test.currentNamespace)
|
||||||
assert.Equal(t, test.expected, got)
|
assert.Equal(t, test.want, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,48 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// resourceRun runs scan on kubernetes cluster
|
// resourceRun runs scan on kubernetes cluster
|
||||||
func resourceRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
|
func resourceRun(ctx context.Context, args []string, opts flag.Options, cluster k8s.Cluster) error {
|
||||||
kind, name, err := extractKindAndName(cliCtx.Args().Slice())
|
kind, name, err := extractKindAndName(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opt, cluster.GetCurrentNamespace()))
|
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opts, cluster.GetCurrentNamespace()))
|
||||||
|
|
||||||
if len(name) == 0 { // pods or configmaps etc
|
if len(name) == 0 { // pods or configmaps etc
|
||||||
if err := validateReportArguments(cliCtx); err != nil {
|
if err = validateReportArguments(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targets, err := trivyk8s.Resources(kind).ListArtifacts(cliCtx.Context)
|
targets, err := trivyk8s.Resources(kind).ListArtifacts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), targets)
|
return run(ctx, opts, cluster.GetCurrentContext(), targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pod/NAME or pod NAME etc
|
// pod/NAME or pod NAME etc
|
||||||
artifact, err := trivyk8s.GetArtifact(cliCtx.Context, kind, name)
|
artifact, err := trivyk8s.GetArtifact(ctx, kind, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), []*artifacts.Artifact{artifact})
|
return run(ctx, opts, cluster.GetCurrentContext(), []*artifacts.Artifact{artifact})
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractKindAndName(args []string) (string, string, error) {
|
func extractKindAndName(args []string) (string, string, error) {
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||||
@@ -22,29 +25,24 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Run runs a k8s scan
|
// Run runs a k8s scan
|
||||||
func Run(cliCtx *cli.Context) error {
|
func Run(ctx context.Context, args []string, opts flag.Options) error {
|
||||||
opt, err := cmd.InitOption(cliCtx)
|
cluster, err := k8s.GetCluster(opts.K8sOptions.ClusterContext)
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("option error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster, err := k8s.GetCluster(opt.KubernetesOption.ClusterContext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("failed getting k8s cluster: %w", err)
|
return xerrors.Errorf("failed getting k8s cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cliCtx.Args().Get(0) {
|
switch args[0] {
|
||||||
case clusterArtifact:
|
case clusterArtifact:
|
||||||
return clusterRun(cliCtx, opt, cluster)
|
return clusterRun(ctx, opts, cluster)
|
||||||
case allArtifact:
|
case allArtifact:
|
||||||
return namespaceRun(cliCtx, opt, cluster)
|
return namespaceRun(ctx, opts, cluster)
|
||||||
default: // resourceArtifact
|
default: // resourceArtifact
|
||||||
return resourceRun(cliCtx, opt, cluster)
|
return resourceRun(ctx, args, opts, cluster)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artifacts.Artifact) error {
|
func run(ctx context.Context, opts flag.Options, cluster string, artifacts []*artifacts.Artifact) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
|
ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -54,7 +52,7 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
runner, err := cmd.NewRunner(opt)
|
runner, err := cmd.NewRunner(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, cmd.SkipScan) {
|
if errors.Is(err, cmd.SkipScan) {
|
||||||
return nil
|
return nil
|
||||||
@@ -67,22 +65,22 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s := scanner.NewScanner(cluster, runner, opt)
|
s := scanner.NewScanner(cluster, runner, opts)
|
||||||
|
|
||||||
r, err := s.Scan(ctx, artifacts)
|
r, err := s.Scan(ctx, artifacts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("k8s scan error: %w", err)
|
return xerrors.Errorf("k8s scan error: %w", err)
|
||||||
}
|
}
|
||||||
if err := report.Write(r, report.Option{
|
if err := report.Write(r, report.Option{
|
||||||
Format: opt.Format,
|
Format: opts.Format,
|
||||||
Report: opt.KubernetesOption.ReportFormat,
|
Report: opts.ReportFormat,
|
||||||
Output: opt.Output,
|
Output: opts.Output,
|
||||||
Severities: opt.Severities,
|
Severities: opts.Severities,
|
||||||
}, opt.ReportOption.SecurityChecks); err != nil {
|
}, opts.ScanOptions.SecurityChecks); err != nil {
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
return xerrors.Errorf("unable to write results: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Exit(opt, r.Failed())
|
cmd.Exit(opts, r.Failed())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -101,10 +99,10 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
|
|||||||
// Single resource scanning is allowed with implicit "--report all".
|
// Single resource scanning is allowed with implicit "--report all".
|
||||||
//
|
//
|
||||||
// e.g. $ trivy k8s pod myapp
|
// e.g. $ trivy k8s pod myapp
|
||||||
func validateReportArguments(cliCtx *cli.Context) error {
|
func validateReportArguments(opts flag.Options) error {
|
||||||
if cliCtx.String("report") == "all" &&
|
if opts.ReportFormat == "all" &&
|
||||||
!cliCtx.IsSet("report") &&
|
!viper.IsSet("report") &&
|
||||||
cliCtx.String("format") == "table" {
|
opts.Format == "table" {
|
||||||
|
|
||||||
m := "All the results in the table format can mess up your terminal. Use \"--report all\" to tell Trivy to output it to your terminal anyway, or consider \"--report summary\" to show the summary output."
|
m := "All the results in the table format can mess up your terminal. Use \"--report all\" to tell Trivy to output it to your terminal anyway, or consider \"--report summary\" to show the summary output."
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ package report
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -214,42 +213,42 @@ func Test_separateMisConfigRoleAssessment(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
k8sReport Report
|
k8sReport Report
|
||||||
rp option.ReportOption
|
opts flag.ScanOptions
|
||||||
wantRbacReport Report
|
wantRbacReport Report
|
||||||
wantMisConfigReport Report
|
wantMisConfigReport Report
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Role and Deployment Reports",
|
name: "Role and Deployment Reports",
|
||||||
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"config", "rbac"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{"config", "rbac"}},
|
||||||
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
|
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
|
||||||
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
|
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Role Report Only",
|
name: "Role Report Only",
|
||||||
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"rbac"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{"rbac"}},
|
||||||
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
|
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
|
||||||
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
|
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Deployment Report Only",
|
name: "Deployment Report Only",
|
||||||
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"config"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{"config"}},
|
||||||
wantRbacReport: Report{Misconfigurations: []Resource{}},
|
wantRbacReport: Report{Misconfigurations: []Resource{}},
|
||||||
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
|
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "No Deployment & No Role Reports",
|
name: "No Deployment & No Role Reports",
|
||||||
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"vuln"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{"vuln"}},
|
||||||
wantRbacReport: Report{Misconfigurations: []Resource{}},
|
wantRbacReport: Report{Misconfigurations: []Resource{}},
|
||||||
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
|
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
misConfig, rbac := separateMisConfigRoleAssessment(tt.k8sReport, tt.rp.SecurityChecks)
|
misConfig, rbac := separateMisConfigRoleAssessment(tt.k8sReport, tt.opts.SecurityChecks)
|
||||||
assert.Equal(t, len(tt.wantMisConfigReport.Misconfigurations), len(misConfig.Misconfigurations))
|
assert.Equal(t, len(tt.wantMisConfigReport.Misconfigurations), len(misConfig.Misconfigurations))
|
||||||
assert.Equal(t, len(tt.wantRbacReport.Misconfigurations), len(rbac.Misconfigurations))
|
assert.Equal(t, len(tt.wantRbacReport.Misconfigurations), len(rbac.Misconfigurations))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,43 +6,46 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReport_ColumnHeading(t *testing.T) {
|
func TestReport_ColumnHeading(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
rp option.ReportOption
|
opts flag.ScanOptions
|
||||||
availableColumns []string
|
availableColumns []string
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "all workload columns",
|
name: "all workload columns",
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"vuln", "config", "secret", "rbac"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability,
|
||||||
|
types.SecurityCheckConfig, types.SecurityCheckSecret, types.SecurityCheckRbac}},
|
||||||
availableColumns: WorkloadColumns(),
|
availableColumns: WorkloadColumns(),
|
||||||
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn, MisconfigurationsColumn, SecretsColumn},
|
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn, MisconfigurationsColumn, SecretsColumn},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all rbac columns",
|
name: "all rbac columns",
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"vuln", "config", "secret", "rbac"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability,
|
||||||
|
types.SecurityCheckConfig, types.SecurityCheckSecret, types.SecurityCheckRbac}},
|
||||||
availableColumns: RoleColumns(),
|
availableColumns: RoleColumns(),
|
||||||
want: []string{NamespaceColumn, ResourceColumn, RbacAssessmentColumn},
|
want: []string{NamespaceColumn, ResourceColumn, RbacAssessmentColumn},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "config column only",
|
name: "config column only",
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"config"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckConfig}},
|
||||||
availableColumns: WorkloadColumns(),
|
availableColumns: WorkloadColumns(),
|
||||||
want: []string{NamespaceColumn, ResourceColumn, MisconfigurationsColumn},
|
want: []string{NamespaceColumn, ResourceColumn, MisconfigurationsColumn},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "secret column only",
|
name: "secret column only",
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"secret"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckSecret}},
|
||||||
availableColumns: WorkloadColumns(),
|
availableColumns: WorkloadColumns(),
|
||||||
want: []string{NamespaceColumn, ResourceColumn, SecretsColumn},
|
want: []string{NamespaceColumn, ResourceColumn, SecretsColumn},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vuln column only",
|
name: "vuln column only",
|
||||||
rp: option.ReportOption{SecurityChecks: []string{"vuln"}},
|
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability}},
|
||||||
availableColumns: WorkloadColumns(),
|
availableColumns: WorkloadColumns(),
|
||||||
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn},
|
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn},
|
||||||
},
|
},
|
||||||
@@ -50,7 +53,7 @@ func TestReport_ColumnHeading(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) {
|
||||||
column := ColumnHeading(tt.rp.SecurityChecks, tt.availableColumns)
|
column := ColumnHeading(tt.opts.SecurityChecks, tt.availableColumns)
|
||||||
if !assert.Equal(t, column, tt.want) {
|
if !assert.Equal(t, column, tt.want) {
|
||||||
t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column))
|
t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,28 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/flag"
|
||||||
"github.com/aquasecurity/trivy/pkg/k8s/report"
|
"github.com/aquasecurity/trivy/pkg/k8s/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
cluster string
|
cluster string
|
||||||
runner cmd.Runner
|
runner cmd.Runner
|
||||||
opt cmd.Option
|
opts flag.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScanner(cluster string, runner cmd.Runner, opt cmd.Option) *Scanner {
|
func NewScanner(cluster string, runner cmd.Runner, opts flag.Options) *Scanner {
|
||||||
return &Scanner{cluster, runner, opt}
|
return &Scanner{cluster, runner, opts}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (report.Report, error) {
|
func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (report.Report, error) {
|
||||||
// progress bar
|
// progress bar
|
||||||
bar := pb.StartNew(len(artifacts))
|
bar := pb.StartNew(len(artifacts))
|
||||||
if s.opt.NoProgress {
|
if s.opts.NoProgress {
|
||||||
bar.SetWriter(io.Discard)
|
bar.SetWriter(io.Discard)
|
||||||
}
|
}
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
@@ -37,7 +37,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
|
|||||||
var vulns, misconfigs []report.Resource
|
var vulns, misconfigs []report.Resource
|
||||||
|
|
||||||
// disable logs before scanning
|
// disable logs before scanning
|
||||||
err := log.InitLogger(s.opt.Debug, true)
|
err := log.InitLogger(s.opts.Debug, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return report.Report{}, xerrors.Errorf("logger error: %w", err)
|
return report.Report{}, xerrors.Errorf("logger error: %w", err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
|
|||||||
// to enable logs even when the function returns earlier
|
// to enable logs even when the function returns earlier
|
||||||
// due to an error
|
// due to an error
|
||||||
defer func() {
|
defer func() {
|
||||||
err = log.InitLogger(s.opt.Debug, false)
|
err = log.InitLogger(s.opts.Debug, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we use log.Fatal here because the error was to enable the logger
|
// we use log.Fatal here because the error was to enable the logger
|
||||||
log.Fatal(xerrors.Errorf("can't enable logger error: %w", err))
|
log.Fatal(xerrors.Errorf("can't enable logger error: %w", err))
|
||||||
@@ -58,7 +58,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
|
|||||||
for _, artifact := range artifacts {
|
for _, artifact := range artifacts {
|
||||||
bar.Increment()
|
bar.Increment()
|
||||||
|
|
||||||
if slices.Contains(s.opt.SecurityChecks, types.SecurityCheckVulnerability) {
|
if slices.Contains(s.opts.SecurityChecks, types.SecurityCheckVulnerability) {
|
||||||
resources, err := s.scanVulns(ctx, artifact)
|
resources, err := s.scanVulns(ctx, artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return report.Report{}, xerrors.Errorf("scanning vulnerabilities error: %w", err)
|
return report.Report{}, xerrors.Errorf("scanning vulnerabilities error: %w", err)
|
||||||
@@ -66,7 +66,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
|
|||||||
vulns = append(vulns, resources...)
|
vulns = append(vulns, resources...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.shouldScanMisconfig(s.opt.SecurityChecks) {
|
if s.shouldScanMisconfig(s.opts.SecurityChecks) {
|
||||||
resource, err := s.scanMisconfigs(ctx, artifact)
|
resource, err := s.scanMisconfigs(ctx, artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return report.Report{}, xerrors.Errorf("scanning misconfigurations error: %w", err)
|
return report.Report{}, xerrors.Errorf("scanning misconfigurations error: %w", err)
|
||||||
@@ -92,9 +92,9 @@ func (s *Scanner) scanVulns(ctx context.Context, artifact *artifacts.Artifact) (
|
|||||||
|
|
||||||
for _, image := range artifact.Images {
|
for _, image := range artifact.Images {
|
||||||
|
|
||||||
s.opt.Target = image
|
s.opts.Target = image
|
||||||
|
|
||||||
imageReport, err := s.runner.ScanImage(ctx, s.opt)
|
imageReport, err := s.runner.ScanImage(ctx, s.opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logger.Debugf("failed to scan image %s: %s", image, err)
|
log.Logger.Debugf("failed to scan image %s: %s", image, err)
|
||||||
@@ -119,9 +119,9 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifa
|
|||||||
return report.Resource{}, xerrors.Errorf("scan error: %w", err)
|
return report.Resource{}, xerrors.Errorf("scan error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.opt.Target = configFile
|
s.opts.Target = configFile
|
||||||
|
|
||||||
configReport, err := s.runner.ScanFilesystem(ctx, s.opt)
|
configReport, err := s.runner.ScanFilesystem(ctx, s.opts)
|
||||||
//remove config file after scanning
|
//remove config file after scanning
|
||||||
removeFile(configFile)
|
removeFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,7 +133,7 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) {
|
func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) {
|
||||||
r, err := s.runner.Filter(ctx, s.opt, r)
|
r, err := s.runner.Filter(ctx, s.opts, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return report.Resource{}, xerrors.Errorf("filter error: %w", err)
|
return report.Resource{}, xerrors.Errorf("filter error: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,19 @@ func LoadAll() ([]Plugin, error) {
|
|||||||
return plugins, nil
|
return plugins, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunWithArgs runs the plugin with arguments
|
||||||
|
func RunWithArgs(ctx context.Context, url string, args []string) error {
|
||||||
|
pl, err := Install(ctx, url, false)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("plugin install error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = pl.Run(ctx, args); err != nil {
|
||||||
|
return xerrors.Errorf("unable to run %s plugin: %w", pl.Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadMetadata(dir string) (Plugin, error) {
|
func loadMetadata(dir string) (Plugin, error) {
|
||||||
filePath := filepath.Join(dir, configFile)
|
filePath := filepath.Join(dir, configFile)
|
||||||
f, err := os.Open(filePath)
|
f, err := os.Open(filePath)
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const (
|
|||||||
FormatGitHub = "github"
|
FormatGitHub = "github"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SupportedSBOMFormats = []string{FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub}
|
||||||
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
AppVersion string
|
AppVersion string
|
||||||
|
|
||||||
|
|||||||
@@ -97,12 +97,12 @@ func (u *Unmarshaler) Unmarshal(r io.Reader) (sbom.SBOM, error) {
|
|||||||
if bom.Metadata != nil {
|
if bom.Metadata != nil {
|
||||||
metadata.Timestamp = bom.Metadata.Timestamp
|
metadata.Timestamp = bom.Metadata.Timestamp
|
||||||
if bom.Metadata.Component != nil {
|
if bom.Metadata.Component != nil {
|
||||||
metadata.Component = toTrivyCdxComponent(fromPtr(bom.Metadata.Component))
|
metadata.Component = toTrivyCdxComponent(lo.FromPtr(bom.Metadata.Component))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var components []ftypes.Component
|
var components []ftypes.Component
|
||||||
for _, c := range fromPtr(bom.Components) {
|
for _, c := range lo.FromPtr(bom.Components) {
|
||||||
components = append(components, toTrivyCdxComponent(c))
|
components = append(components, toTrivyCdxComponent(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ func (u *Unmarshaler) walkDependencies(rootRef string) []cdx.Component {
|
|||||||
func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[string]cdx.Component {
|
func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[string]cdx.Component {
|
||||||
cmap := make(map[string]cdx.Component)
|
cmap := make(map[string]cdx.Component)
|
||||||
|
|
||||||
for _, component := range fromPtr(components) {
|
for _, component := range lo.FromPtr(components) {
|
||||||
cmap[component.BOMRef] = component
|
cmap[component.BOMRef] = component
|
||||||
}
|
}
|
||||||
if metadata != nil {
|
if metadata != nil {
|
||||||
@@ -208,13 +208,13 @@ func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[strin
|
|||||||
func dependencyMap(deps *[]cdx.Dependency) map[string][]string {
|
func dependencyMap(deps *[]cdx.Dependency) map[string][]string {
|
||||||
depMap := make(map[string][]string)
|
depMap := make(map[string][]string)
|
||||||
|
|
||||||
for _, dep := range fromPtr(deps) {
|
for _, dep := range lo.FromPtr(deps) {
|
||||||
if _, ok := depMap[dep.Ref]; ok {
|
if _, ok := depMap[dep.Ref]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var refs []string
|
var refs []string
|
||||||
for _, d := range fromPtr(dep.Dependencies) {
|
for _, d := range lo.FromPtr(dep.Dependencies) {
|
||||||
refs = append(refs, d.Ref)
|
refs = append(refs, d.Ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,11 +270,11 @@ func toPackage(component cdx.Component) (string, *ftypes.Package, error) {
|
|||||||
pkg := p.Package()
|
pkg := p.Package()
|
||||||
pkg.Ref = component.BOMRef
|
pkg.Ref = component.BOMRef
|
||||||
|
|
||||||
for _, license := range fromPtr(component.Licenses) {
|
for _, license := range lo.FromPtr(component.Licenses) {
|
||||||
pkg.Licenses = append(pkg.Licenses, license.Expression)
|
pkg.Licenses = append(pkg.Licenses, license.Expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, prop := range fromPtr(component.Properties) {
|
for _, prop := range lo.FromPtr(component.Properties) {
|
||||||
if strings.HasPrefix(prop.Name, Namespace) {
|
if strings.HasPrefix(prop.Name, Namespace) {
|
||||||
switch strings.TrimPrefix(prop.Name, Namespace) {
|
switch strings.TrimPrefix(prop.Name, Namespace) {
|
||||||
case PropertySrcName:
|
case PropertySrcName:
|
||||||
@@ -311,18 +311,10 @@ func toTrivyCdxComponent(component cdx.Component) ftypes.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lookupProperty(properties *[]cdx.Property, key string) string {
|
func lookupProperty(properties *[]cdx.Property, key string) string {
|
||||||
for _, p := range fromPtr(properties) {
|
for _, p := range lo.FromPtr(properties) {
|
||||||
if p.Name == Namespace+key {
|
if p.Name == Namespace+key {
|
||||||
return p.Value
|
return p.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromPtr[T any](ptr *T) T {
|
|
||||||
if ptr == nil {
|
|
||||||
var t T
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
return *ptr
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user